From 175cfb006d9be84ce2c99350baaa5118c4b8001c Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 10:44:27 +0000 Subject: [PATCH 01/49] feat: add usage alerts schema and database support --- .../apiCusUtils/getApiCustomerBase.ts | 1 + .../billingControls/entityBillingControls.ts | 4 +++ shared/api/billingControls/index.ts | 1 + shared/api/billingControls/usageAlert.ts | 6 +++++ .../customerBillingControls.ts | 7 +++++- .../billingControls/entityBillingControls.ts | 4 +++ .../cusModels/billingControls/usageAlert.ts | 25 +++++++++++++++++++ shared/models/cusModels/cusModels.ts | 2 ++ shared/models/cusModels/cusTable.ts | 2 ++ .../cusModels/entityModels/entityModels.ts | 6 ++++- .../cusModels/entityModels/entityTable.ts | 6 ++++- 11 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 shared/api/billingControls/usageAlert.ts create mode 100644 shared/models/cusModels/billingControls/usageAlert.ts diff --git a/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts b/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts index 30c5e8974..fbb187d48 100644 --- a/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts +++ b/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts @@ -68,6 +68,7 @@ export const getApiCustomerBase = async ({ billing_controls: { auto_topups: fullCus.auto_topups ?? undefined, spend_limits: fullCus.spend_limits ?? undefined, + usage_alerts: fullCus.usage_alerts ?? undefined, }, invoices: diff --git a/shared/api/billingControls/entityBillingControls.ts b/shared/api/billingControls/entityBillingControls.ts index 693f5b15a..179d63638 100644 --- a/shared/api/billingControls/entityBillingControls.ts +++ b/shared/api/billingControls/entityBillingControls.ts @@ -1,10 +1,14 @@ import { z } from "zod/v4"; import { ApiSpendLimitSchema } from "./spendLimit.js"; +import { ApiUsageAlertSchema } from "./usageAlert.js"; export const ApiEntityBillingControlsSchema = z.object({ spend_limits: z.array(ApiSpendLimitSchema).optional().meta({ description: "List of overage spend limits per feature.", }), + usage_alerts: z.array(ApiUsageAlertSchema).optional().meta({ + description: "List of usage alert configurations per feature.", + }), }); export const ApiEntityBillingControlsParamsSchema = diff --git a/shared/api/billingControls/index.ts b/shared/api/billingControls/index.ts index 3e1cd4fa2..9beba0431 100644 --- a/shared/api/billingControls/index.ts +++ b/shared/api/billingControls/index.ts @@ -1,2 +1,3 @@ export * from "./entityBillingControls.js"; export * from "./spendLimit.js"; +export * from "./usageAlert.js"; diff --git a/shared/api/billingControls/usageAlert.ts b/shared/api/billingControls/usageAlert.ts new file mode 100644 index 000000000..3f49d069b --- /dev/null +++ b/shared/api/billingControls/usageAlert.ts @@ -0,0 +1,6 @@ +import type { z } from "zod/v4"; +import { DbUsageAlertSchema } from "../../models/cusModels/billingControls/usageAlert.js"; + +export const ApiUsageAlertSchema = DbUsageAlertSchema; + +export type ApiUsageAlert = z.infer; diff --git a/shared/models/cusModels/billingControls/customerBillingControls.ts b/shared/models/cusModels/billingControls/customerBillingControls.ts index ce52b2214..6ef0eb105 100644 --- a/shared/models/cusModels/billingControls/customerBillingControls.ts +++ b/shared/models/cusModels/billingControls/customerBillingControls.ts @@ -6,6 +6,7 @@ import { } from "./entityBillingControls.js"; import { PurchaseLimitIntervalEnum } from "./purchaseLimitInterval.js"; import { type DbSpendLimit, DbSpendLimitSchema } from "./spendLimit.js"; +import { type DbUsageAlert, DbUsageAlertSchema } from "./usageAlert.js"; export const AutoTopupPurchaseLimitSchema = z.object({ interval: PurchaseLimitIntervalEnum.meta({ @@ -45,6 +46,9 @@ export const CustomerBillingControlsSchema = z.object({ spend_limits: z.array(DbSpendLimitSchema).optional().meta({ description: "List of overage spend limits per feature.", }), + usage_alerts: z.array(DbUsageAlertSchema).optional().meta({ + description: "List of usage alert configurations per feature.", + }), }); export const CustomerBillingControlsParamsSchema = @@ -85,9 +89,10 @@ export type CustomerBillingControlsParams = z.input< typeof CustomerBillingControlsParamsSchema >; -export { EntityBillingControlsSchema, DbSpendLimitSchema }; +export { EntityBillingControlsSchema, DbSpendLimitSchema, DbUsageAlertSchema }; export type { EntityBillingControls, EntityBillingControlsParams, DbSpendLimit, + DbUsageAlert, }; diff --git a/shared/models/cusModels/billingControls/entityBillingControls.ts b/shared/models/cusModels/billingControls/entityBillingControls.ts index afde88943..d815fcd2a 100644 --- a/shared/models/cusModels/billingControls/entityBillingControls.ts +++ b/shared/models/cusModels/billingControls/entityBillingControls.ts @@ -1,10 +1,14 @@ import { z } from "zod/v4"; import { DbSpendLimitSchema } from "./spendLimit.js"; +import { DbUsageAlertSchema } from "./usageAlert.js"; export const EntityBillingControlsSchema = z.object({ spend_limits: z.array(DbSpendLimitSchema).optional().meta({ description: "List of overage spend limits per feature.", }), + usage_alerts: z.array(DbUsageAlertSchema).optional().meta({ + description: "List of usage alert configurations per feature.", + }), }); export type EntityBillingControls = z.infer; diff --git a/shared/models/cusModels/billingControls/usageAlert.ts b/shared/models/cusModels/billingControls/usageAlert.ts new file mode 100644 index 000000000..630041adc --- /dev/null +++ b/shared/models/cusModels/billingControls/usageAlert.ts @@ -0,0 +1,25 @@ +import { z } from "zod/v4"; + +export const UsageAlertThresholdType = z.enum(["usage", "balance"]); + +export const DbUsageAlertSchema = z.object({ + feature_id: z.string().optional().meta({ + description: "The feature id this alert applies to. If not included, the alert applies globally.", + }), + enabled: z.boolean().default(true).meta({ + description: "Whether this usage alert is enabled.", + }), + threshold: z.number().min(0).meta({ + description: "The threshold value that triggers the alert.", + }), + threshold_type: UsageAlertThresholdType.meta({ + description: + 'Whether the threshold is based on, can be usage or balance.', + }), + name: z.string().optional().meta({ + description: + "Optional user-defined label to name an alerts.", + }), +}); + +export type DbUsageAlert = z.infer; diff --git a/shared/models/cusModels/cusModels.ts b/shared/models/cusModels/cusModels.ts index f51c0b704..e96d2d356 100644 --- a/shared/models/cusModels/cusModels.ts +++ b/shared/models/cusModels/cusModels.ts @@ -4,6 +4,7 @@ import { ExternalProcessorsSchema } from "../genModels/processorSchemas.js"; import { AutoTopupSchema, DbSpendLimitSchema, + DbUsageAlertSchema, } from "./billingControls/customerBillingControls.js"; export const CustomerSchema = z.object({ @@ -23,6 +24,7 @@ export const CustomerSchema = z.object({ send_email_receipts: z.boolean().default(false), auto_topups: z.array(AutoTopupSchema).nullish(), spend_limits: z.array(DbSpendLimitSchema).nullish(), + usage_alerts: z.array(DbUsageAlertSchema).nullish(), }); export type Customer = z.infer; diff --git a/shared/models/cusModels/cusTable.ts b/shared/models/cusModels/cusTable.ts index da280c3b4..f73d5544c 100644 --- a/shared/models/cusModels/cusTable.ts +++ b/shared/models/cusModels/cusTable.ts @@ -16,6 +16,7 @@ import { organizations } from "../orgModels/orgTable.js"; import type { AutoTopup, DbSpendLimit, + DbUsageAlert, } from "./billingControls/customerBillingControls.js"; export type CustomerProcessor = { @@ -42,6 +43,7 @@ export const customers = pgTable( send_email_receipts: boolean("send_email_receipts").default(false), auto_topups: jsonb().$type(), spend_limits: jsonb().$type(), + usage_alerts: jsonb().$type(), }, (table) => [ unique("cus_id_constraint").on(table.org_id, table.id, table.env), diff --git a/shared/models/cusModels/entityModels/entityModels.ts b/shared/models/cusModels/entityModels/entityModels.ts index f63ffbace..25c8413f0 100644 --- a/shared/models/cusModels/entityModels/entityModels.ts +++ b/shared/models/cusModels/entityModels/entityModels.ts @@ -1,6 +1,9 @@ import { z } from "zod/v4"; import type { Feature } from "../../featureModels/featureModels.js"; -import { DbSpendLimitSchema } from "../billingControls/customerBillingControls.js"; +import { + DbSpendLimitSchema, + DbUsageAlertSchema, +} from "../billingControls/customerBillingControls.js"; export const EntitySchema = z.object({ id: z.string().nullable(), @@ -14,6 +17,7 @@ export const EntitySchema = z.object({ feature_id: z.string(), internal_feature_id: z.string(), spend_limits: z.array(DbSpendLimitSchema).nullish(), + usage_alerts: z.array(DbUsageAlertSchema).nullish(), }); // export const CreateEntitySchema = z.object({ diff --git a/shared/models/cusModels/entityModels/entityTable.ts b/shared/models/cusModels/entityModels/entityTable.ts index 9715bef54..5cfe847b6 100644 --- a/shared/models/cusModels/entityModels/entityTable.ts +++ b/shared/models/cusModels/entityModels/entityTable.ts @@ -11,7 +11,10 @@ import { } from "drizzle-orm/pg-core"; import { features } from "../../featureModels/featureTable.js"; import { organizations } from "../../orgModels/orgTable.js"; -import type { DbSpendLimit } from "../billingControls/customerBillingControls.js"; +import type { + DbSpendLimit, + DbUsageAlert, +} from "../billingControls/customerBillingControls.js"; import { customers } from "../cusTable.js"; export const entities = pgTable( @@ -27,6 +30,7 @@ export const entities = pgTable( deleted: boolean().default(false).notNull(), internal_feature_id: text("internal_feature_id"), spend_limits: jsonb().$type(), + usage_alerts: jsonb().$type(), // Optional... feature_id: text("feature_id"), From 3a8c13ac80cbf0618efece1ed2d9bf0783f850b7 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 10:50:14 +0000 Subject: [PATCH 02/49] feat: include database migration files --- shared/drizzle/0000_rainy_siren.sql | 746 ++++ shared/drizzle/0001_fixed_whizzer.sql | 2 + shared/drizzle/meta/0000_snapshot.json | 5264 +++++++++++++++++++++++ shared/drizzle/meta/0001_snapshot.json | 5276 ++++++++++++++++++++++++ shared/drizzle/meta/_journal.json | 20 + 5 files changed, 11308 insertions(+) create mode 100644 shared/drizzle/0000_rainy_siren.sql create mode 100644 shared/drizzle/0001_fixed_whizzer.sql create mode 100644 shared/drizzle/meta/0000_snapshot.json create mode 100644 shared/drizzle/meta/0001_snapshot.json create mode 100644 shared/drizzle/meta/_journal.json diff --git a/shared/drizzle/0000_rainy_siren.sql b/shared/drizzle/0000_rainy_siren.sql new file mode 100644 index 000000000..c6c036cf2 --- /dev/null +++ b/shared/drizzle/0000_rainy_siren.sql @@ -0,0 +1,746 @@ +CREATE TABLE "account" ( + "id" text PRIMARY KEY NOT NULL, + "account_id" text NOT NULL, + "provider_id" text NOT NULL, + "user_id" text NOT NULL, + "access_token" text, + "refresh_token" text, + "id_token" text, + "access_token_expires_at" timestamp, + "refresh_token_expires_at" timestamp, + "scope" text, + "password" text, + "created_at" timestamp with time zone NOT NULL, + "updated_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +ALTER TABLE "account" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "actions" ( + "id" text COLLATE "C" PRIMARY KEY NOT NULL, + "request_id" text NOT NULL, + "org_id" text NOT NULL, + "org_slug" text NOT NULL, + "env" text NOT NULL, + "customer_id" text, + "internal_customer_id" text, + "entity_id" text, + "internal_entity_id" text, + "type" text NOT NULL, + "auth_type" text NOT NULL, + "method" text NOT NULL, + "path" text NOT NULL, + "timestamp" timestamp with time zone DEFAULT now() NOT NULL, + "properties" jsonb +); +--> statement-breakpoint +ALTER TABLE "actions" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "api_keys" ( + "id" text COLLATE "C" PRIMARY KEY NOT NULL, + "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, + "name" text, + "prefix" text, + "org_id" text, + "user_id" text, + "env" text, + "hashed_key" text, + "meta" jsonb, + CONSTRAINT "api_keys_hashed_key_key" UNIQUE("hashed_key") +); +--> statement-breakpoint +CREATE TABLE "auto_topup_limit_states" ( + "id" text PRIMARY KEY NOT NULL, + "org_id" text NOT NULL, + "env" text NOT NULL, + "internal_customer_id" text NOT NULL, + "customer_id" text NOT NULL, + "feature_id" text NOT NULL, + "purchase_window_ends_at" numeric NOT NULL, + "purchase_count" numeric DEFAULT 0 NOT NULL, + "attempt_window_ends_at" numeric NOT NULL, + "attempt_count" numeric DEFAULT 0 NOT NULL, + "failed_attempt_window_ends_at" numeric NOT NULL, + "failed_attempt_count" numeric DEFAULT 0 NOT NULL, + "last_attempt_at" numeric, + "last_failed_attempt_at" numeric, + "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, + "updated_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL +); +--> statement-breakpoint +CREATE TABLE "chat_results" ( + "id" text COLLATE "C" PRIMARY KEY NOT NULL, + "created_at" numeric, + "data" jsonb NOT NULL +); +--> statement-breakpoint +ALTER TABLE "chat_results" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "checkouts" ( + "id" text PRIMARY KEY NOT NULL, + "org_id" text NOT NULL, + "env" text NOT NULL, + "internal_customer_id" text NOT NULL, + "customer_id" text NOT NULL, + "action" text NOT NULL, + "params" jsonb NOT NULL, + "params_version" integer DEFAULT 0 NOT NULL, + "status" text DEFAULT 'pending' NOT NULL, + "response" jsonb, + "stripe_invoice_id" text, + "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, + "expires_at" numeric NOT NULL, + "completed_at" numeric +); +--> statement-breakpoint +CREATE TABLE "customer_entitlements" ( + "id" text COLLATE "C" PRIMARY KEY NOT NULL, + "customer_product_id" text, + "entitlement_id" text NOT NULL, + "internal_customer_id" text COLLATE "C" NOT NULL, + "internal_entity_id" text, + "internal_feature_id" text NOT NULL, + "unlimited" boolean DEFAULT false, + "balance" numeric DEFAULT 0 NOT NULL, + "created_at" numeric NOT NULL, + "next_reset_at" numeric, + "usage_allowed" boolean DEFAULT false, + "adjustment" numeric, + "additional_balance" numeric DEFAULT 0 NOT NULL, + "entities" jsonb, + "expires_at" numeric, + "cache_version" integer DEFAULT 0, + "customer_id" text, + "feature_id" text, + "external_id" text +); +--> statement-breakpoint +CREATE TABLE "customer_prices" ( + "id" text PRIMARY KEY NOT NULL, + "created_at" numeric NOT NULL, + "price_id" text, + "options" jsonb, + "internal_customer_id" text, + "customer_product_id" text +); +--> statement-breakpoint +CREATE TABLE "customer_products" ( + "id" text COLLATE "C" PRIMARY KEY NOT NULL, + "internal_customer_id" text NOT NULL, + "internal_product_id" text NOT NULL, + "internal_entity_id" text, + "created_at" numeric, + "status" text, + "processor" jsonb, + "canceled" boolean DEFAULT false, + "canceled_at" numeric, + "ended_at" numeric, + "starts_at" numeric, + "options" jsonb[], + "product_id" text, + "free_trial_id" text, + "trial_ends_at" numeric, + "collection_method" text DEFAULT 'charge_automatically', + "subscription_ids" text[], + "scheduled_ids" text[], + "quantity" numeric DEFAULT 1, + "is_custom" boolean DEFAULT false NOT NULL, + "customer_id" text, + "entity_id" text, + "billing_version" text, + "api_version" numeric, + "api_semver" text, + "external_id" text +); +--> statement-breakpoint +CREATE TABLE "customers" ( + "internal_id" text COLLATE "C" PRIMARY KEY NOT NULL, + "org_id" text NOT NULL, + "created_at" numeric NOT NULL, + "name" text, + "id" text, + "email" text, + "fingerprint" text DEFAULT null, + "metadata" jsonb, + "env" text NOT NULL, + "processor" jsonb, + "processors" jsonb DEFAULT '{}'::jsonb, + "send_email_receipts" boolean DEFAULT false, + "auto_topups" jsonb, + "spend_limits" jsonb, + CONSTRAINT "cus_id_constraint" UNIQUE("org_id","id","env") +); +--> statement-breakpoint +ALTER TABLE "customers" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "entities" ( + "id" text, + "org_id" text, + "created_at" numeric NOT NULL, + "internal_id" text PRIMARY KEY NOT NULL, + "internal_customer_id" text NOT NULL, + "env" text, + "name" text, + "deleted" boolean DEFAULT false NOT NULL, + "internal_feature_id" text, + "spend_limits" jsonb, + "feature_id" text, + CONSTRAINT "entity_id_constraint" UNIQUE("org_id","env","internal_customer_id","id") +); +--> statement-breakpoint +CREATE TABLE "entitlements" ( + "id" text PRIMARY KEY NOT NULL, + "created_at" numeric NOT NULL, + "internal_feature_id" text NOT NULL, + "internal_product_id" text, + "is_custom" boolean DEFAULT false, + "allowance_type" text, + "allowance" numeric, + "interval" text, + "interval_count" numeric DEFAULT 1, + "carry_from_previous" boolean DEFAULT false, + "entity_feature_id" text DEFAULT null, + "org_id" text, + "feature_id" text, + "usage_limit" numeric, + "rollover" jsonb, + CONSTRAINT "entitlements_id_key" UNIQUE("id") +); +--> statement-breakpoint +CREATE TABLE "events" ( + "id" text PRIMARY KEY NOT NULL, + "org_id" text NOT NULL, + "org_slug" text NOT NULL, + "internal_customer_id" text, + "env" text NOT NULL, + "created_at" bigint, + "timestamp" timestamp with time zone, + "event_name" text NOT NULL, + "idempotency_key" text DEFAULT null, + "value" numeric, + "set_usage" boolean DEFAULT false, + "entity_id" text, + "internal_entity_id" text, + "customer_id" text NOT NULL, + "properties" jsonb, + CONSTRAINT "unique_event_constraint" UNIQUE("org_id","env","customer_id","event_name","idempotency_key") +); +--> statement-breakpoint +CREATE TABLE "features" ( + "internal_id" text COLLATE "C" PRIMARY KEY NOT NULL, + "org_id" text NOT NULL, + "created_at" numeric, + "env" text, + "id" text NOT NULL, + "name" text, + "type" text NOT NULL, + "config" jsonb, + "display" jsonb DEFAULT null, + "archived" boolean DEFAULT false NOT NULL, + "event_names" text[] DEFAULT '{}', + CONSTRAINT "feature_id_constraint" UNIQUE("org_id","id","env") +); +--> statement-breakpoint +CREATE TABLE "free_trials" ( + "id" text PRIMARY KEY NOT NULL, + "created_at" numeric NOT NULL, + "internal_product_id" text, + "duration" text DEFAULT 'day', + "length" numeric, + "unique_fingerprint" boolean, + "is_custom" boolean DEFAULT false, + "card_required" boolean DEFAULT false +); +--> statement-breakpoint +CREATE TABLE "invitation" ( + "id" text PRIMARY KEY NOT NULL, + "organization_id" text NOT NULL, + "email" text NOT NULL, + "role" text, + "status" text DEFAULT 'pending' NOT NULL, + "created_at" timestamp with time zone NOT NULL, + "expires_at" timestamp with time zone NOT NULL, + "inviter_id" text NOT NULL +); +--> statement-breakpoint +ALTER TABLE "invitation" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "invoice_line_items" ( + "id" text COLLATE "C" PRIMARY KEY NOT NULL, + "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, + "invoice_id" text, + "stripe_id" text, + "stripe_invoice_id" text, + "stripe_invoice_item_id" text, + "stripe_subscription_item_id" text, + "stripe_product_id" text, + "stripe_price_id" text, + "stripe_discountable" boolean DEFAULT true NOT NULL, + "amount" numeric NOT NULL, + "amount_after_discounts" numeric NOT NULL, + "currency" text DEFAULT 'usd' NOT NULL, + "stripe_quantity" numeric, + "total_quantity" numeric, + "paid_quantity" numeric, + "description" text NOT NULL, + "description_source" text, + "direction" text NOT NULL, + "billing_timing" text, + "prorated" boolean DEFAULT false NOT NULL, + "price_id" text, + "customer_product_ids" jsonb DEFAULT '[]'::jsonb NOT NULL, + "customer_price_ids" jsonb DEFAULT '[]'::jsonb NOT NULL, + "customer_entitlement_ids" jsonb DEFAULT '[]'::jsonb NOT NULL, + "internal_product_id" text, + "product_id" text, + "internal_feature_id" text, + "feature_id" text, + "effective_period_start" numeric, + "effective_period_end" numeric, + "discounts" jsonb[] DEFAULT '{}', + CONSTRAINT "invoice_line_items_stripe_id_unique" UNIQUE("stripe_id") +); +--> statement-breakpoint +CREATE TABLE "invoices" ( + "id" text COLLATE "C" PRIMARY KEY NOT NULL, + "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, + "product_ids" text[] DEFAULT '{}', + "internal_product_ids" text[] DEFAULT '{}', + "internal_customer_id" text NOT NULL, + "internal_entity_id" text, + "stripe_id" text NOT NULL, + "status" text DEFAULT 'draft' NOT NULL, + "hosted_invoice_url" text, + "total" numeric DEFAULT 0 NOT NULL, + "currency" text DEFAULT 'usd' NOT NULL, + "discounts" jsonb[] DEFAULT '{}', + "items" jsonb[] DEFAULT '{}', + CONSTRAINT "invoices_stripe_id_key" UNIQUE("stripe_id") +); +--> statement-breakpoint +CREATE TABLE "jwks" ( + "id" text PRIMARY KEY NOT NULL, + "public_key" text NOT NULL, + "private_key" text NOT NULL, + "created_at" timestamp with time zone NOT NULL, + "expires_at" timestamp with time zone +); +--> statement-breakpoint +ALTER TABLE "jwks" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "member" ( + "id" text PRIMARY KEY NOT NULL, + "organization_id" text NOT NULL, + "user_id" text NOT NULL, + "role" text DEFAULT 'member' NOT NULL, + "created_at" timestamp with time zone NOT NULL +); +--> statement-breakpoint +ALTER TABLE "member" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "metadata" ( + "id" text PRIMARY KEY NOT NULL, + "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, + "expires_at" numeric, + "data" jsonb, + "type" text, + "stripe_invoice_id" text, + "stripe_checkout_session_id" text +); +--> statement-breakpoint +CREATE TABLE "migration_errors" ( + "internal_customer_id" text NOT NULL, + "migration_job_id" text NOT NULL, + "created_at" numeric, + "updated_at" numeric, + "data" jsonb, + "message" text, + "code" text, + CONSTRAINT "migration_errors_pkey" PRIMARY KEY("internal_customer_id","migration_job_id") +); +--> statement-breakpoint +CREATE TABLE "migration_jobs" ( + "id" text PRIMARY KEY NOT NULL, + "org_id" text NOT NULL, + "env" text NOT NULL, + "created_at" numeric NOT NULL, + "updated_at" numeric, + "current_step" text, + "from_internal_product_id" text, + "to_internal_product_id" text, + "step_details" jsonb +); +--> statement-breakpoint +CREATE TABLE "oauth_access_token" ( + "id" text PRIMARY KEY NOT NULL, + "token" text, + "client_id" text NOT NULL, + "session_id" text, + "user_id" text, + "reference_id" text, + "refresh_id" text, + "expires_at" timestamp with time zone, + "created_at" timestamp with time zone, + "scopes" text[] NOT NULL, + CONSTRAINT "oauth_access_token_token_unique" UNIQUE("token") +); +--> statement-breakpoint +ALTER TABLE "oauth_access_token" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "oauth_client" ( + "id" text PRIMARY KEY NOT NULL, + "client_id" text NOT NULL, + "client_secret" text, + "disabled" boolean DEFAULT false, + "skip_consent" boolean, + "enable_end_session" boolean, + "scopes" text[], + "user_id" text, + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone, + "name" text, + "uri" text, + "icon" text, + "contacts" text[], + "tos" text, + "policy" text, + "software_id" text, + "software_version" text, + "software_statement" text, + "redirect_uris" text[] NOT NULL, + "post_logout_redirect_uris" text[], + "token_endpoint_auth_method" text, + "grant_types" text[], + "response_types" text[], + "public" boolean, + "type" text, + "reference_id" text, + "metadata" jsonb, + CONSTRAINT "oauth_client_client_id_unique" UNIQUE("client_id") +); +--> statement-breakpoint +ALTER TABLE "oauth_client" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "oauth_consent" ( + "id" text PRIMARY KEY NOT NULL, + "client_id" text NOT NULL, + "user_id" text, + "reference_id" text, + "scopes" text[] NOT NULL, + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone +); +--> statement-breakpoint +ALTER TABLE "oauth_consent" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "oauth_refresh_token" ( + "id" text PRIMARY KEY NOT NULL, + "token" text NOT NULL, + "client_id" text NOT NULL, + "session_id" text, + "user_id" text NOT NULL, + "reference_id" text, + "expires_at" timestamp with time zone, + "created_at" timestamp with time zone, + "revoked" timestamp with time zone, + "scopes" text[] NOT NULL +); +--> statement-breakpoint +ALTER TABLE "oauth_refresh_token" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "organizations" ( + "id" text PRIMARY KEY NOT NULL, + "slug" text NOT NULL, + "name" text NOT NULL, + "logo" text, + "createdAt" timestamp with time zone NOT NULL, + "metadata" text, + "default_currency" text DEFAULT 'usd', + "stripe_connected" boolean DEFAULT false, + "stripe_config" jsonb, + "test_stripe_connect" jsonb DEFAULT '{}'::jsonb, + "live_stripe_connect" jsonb DEFAULT '{}'::jsonb, + "processor_configs" jsonb, + "test_pkey" text, + "live_pkey" text, + "svix_config" jsonb DEFAULT '{}'::jsonb, + "created_at" numeric, + "config" jsonb DEFAULT '{}'::jsonb NOT NULL, + "created_by" text, + "onboarded" boolean DEFAULT false, + "deployed" boolean DEFAULT false, + CONSTRAINT "organizations_slug_unique" UNIQUE("slug"), + CONSTRAINT "organizations_test_pkey_key" UNIQUE("test_pkey"), + CONSTRAINT "organizations_live_pkey_key" UNIQUE("live_pkey") +); +--> statement-breakpoint +CREATE TABLE "prices" ( + "id" text COLLATE "C" PRIMARY KEY NOT NULL, + "org_id" text NOT NULL, + "internal_product_id" text NOT NULL, + "config" jsonb, + "created_at" numeric NOT NULL, + "billing_type" text, + "tier_behavior" text DEFAULT null, + "is_custom" boolean DEFAULT false, + "entitlement_id" text DEFAULT null, + "proration_config" jsonb DEFAULT null, + CONSTRAINT "prices_id_key" UNIQUE("id") +); +--> statement-breakpoint +CREATE TABLE "products" ( + "internal_id" text PRIMARY KEY NOT NULL, + "id" text NOT NULL, + "name" text, + "description" text, + "org_id" text NOT NULL, + "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, + "env" text NOT NULL, + "is_add_on" boolean DEFAULT false NOT NULL, + "is_default" boolean DEFAULT false NOT NULL, + "group" text DEFAULT '', + "version" numeric DEFAULT 1 NOT NULL, + "processor" jsonb DEFAULT null, + "base_variant_id" text, + "archived" boolean DEFAULT false NOT NULL, + CONSTRAINT "unique_product" UNIQUE("org_id","id","env","version") +); +--> statement-breakpoint +CREATE TABLE "referral_codes" ( + "code" text NOT NULL, + "org_id" text NOT NULL, + "env" text NOT NULL, + "internal_customer_id" text, + "internal_reward_program_id" text, + "id" text NOT NULL, + "created_at" numeric, + CONSTRAINT "referral_codes_pkey" PRIMARY KEY("code","org_id","env"), + CONSTRAINT "referral_codes_id_key" UNIQUE("id") +); +--> statement-breakpoint +CREATE TABLE "replaceables" ( + "id" text PRIMARY KEY NOT NULL, + "cus_ent_id" text NOT NULL, + "created_at" bigint NOT NULL, + "from_entity_id" text, + "delete_next_cycle" boolean DEFAULT false NOT NULL +); +--> statement-breakpoint +ALTER TABLE "replaceables" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "revenuecat_mappings" ( + "org_id" text NOT NULL, + "env" text NOT NULL, + "autumn_product_id" text NOT NULL, + "revenuecat_product_ids" text[] DEFAULT '{}' NOT NULL, + CONSTRAINT "revenuecat_mappings_pkey" PRIMARY KEY("org_id","env","autumn_product_id") +); +--> statement-breakpoint +CREATE TABLE "reward_programs" ( + "internal_id" text PRIMARY KEY NOT NULL, + "id" text, + "created_at" numeric, + "internal_reward_id" text, + "max_redemptions" numeric, + "unlimited_redemptions" boolean DEFAULT false, + "org_id" text, + "env" text, + "when" text DEFAULT 'immediately', + "product_ids" text[] DEFAULT '{""}', + "exclude_trial" boolean DEFAULT false, + "received_by" text +); +--> statement-breakpoint +CREATE TABLE "reward_redemptions" ( + "id" text PRIMARY KEY NOT NULL, + "created_at" numeric, + "updated_at" numeric, + "internal_customer_id" text, + "triggered" boolean, + "internal_reward_program_id" text, + "applied" boolean DEFAULT false, + "redeemer_applied" boolean DEFAULT false, + "referral_code_id" text +); +--> statement-breakpoint +CREATE TABLE "rewards" ( + "internal_id" text PRIMARY KEY NOT NULL, + "id" text, + "org_id" text, + "env" text, + "created_at" numeric, + "name" text, + "discount_config" jsonb, + "free_product_config" jsonb, + "free_product_id" text, + "promo_codes" jsonb[], + "type" text +); +--> statement-breakpoint +CREATE TABLE "rollovers" ( + "id" text PRIMARY KEY NOT NULL, + "cus_ent_id" text NOT NULL, + "balance" numeric NOT NULL, + "expires_at" numeric, + "usage" numeric DEFAULT 0 NOT NULL, + "entities" jsonb DEFAULT '{}'::jsonb NOT NULL +); +--> statement-breakpoint +ALTER TABLE "rollovers" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "session" ( + "id" text PRIMARY KEY NOT NULL, + "expires_at" timestamp with time zone NOT NULL, + "token" text NOT NULL, + "created_at" timestamp with time zone NOT NULL, + "updated_at" timestamp with time zone NOT NULL, + "ip_address" text, + "user_agent" text, + "user_id" text NOT NULL, + "impersonated_by" text, + "active_organization_id" text, + "city" text, + "country" text, + CONSTRAINT "session_token_unique" UNIQUE("token") +); +--> statement-breakpoint +ALTER TABLE "session" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE TABLE "subscriptions" ( + "id" text PRIMARY KEY NOT NULL, + "org_id" text NOT NULL, + "stripe_id" text, + "stripe_schedule_id" text, + "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, + "metadata" jsonb DEFAULT '{}'::jsonb, + "usage_features" text[] DEFAULT '{}', + "env" text, + "current_period_start" numeric, + "current_period_end" numeric, + CONSTRAINT "subscriptions_stripe_id_key" UNIQUE("stripe_id") +); +--> statement-breakpoint +CREATE TABLE "user" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL, + "email_verified" boolean NOT NULL, + "image" text, + "created_at" timestamp with time zone NOT NULL, + "updated_at" timestamp with time zone NOT NULL, + "role" text, + "banned" boolean, + "ban_reason" text, + "ban_expires" timestamp with time zone, + "created_by" text, + "last_active_at" timestamp with time zone, + CONSTRAINT "user_email_unique" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE "vercel_resources" ( + "id" text PRIMARY KEY NOT NULL, + "org_id" text NOT NULL, + "env" text NOT NULL, + "installation_id" text NOT NULL, + "name" text NOT NULL, + "status" text NOT NULL, + "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL +); +--> statement-breakpoint +CREATE TABLE "verification" ( + "id" text PRIMARY KEY NOT NULL, + "identifier" text NOT NULL, + "value" text NOT NULL, + "expires_at" timestamp with time zone NOT NULL, + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone +); +--> statement-breakpoint +ALTER TABLE "verification" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "actions" ADD CONSTRAINT "actions_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "actions" ADD CONSTRAINT "actions_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "actions" ADD CONSTRAINT "actions_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "auto_topup_limit_states" ADD CONSTRAINT "auto_topup_limits_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "auto_topup_limit_states" ADD CONSTRAINT "auto_topup_limits_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "customer_entitlements" ADD CONSTRAINT "entitlements_internal_feature_id_fkey" FOREIGN KEY ("internal_feature_id") REFERENCES "public"."features"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "customer_entitlements" ADD CONSTRAINT "customer_entitlements_internal_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "customer_entitlements" ADD CONSTRAINT "customer_entitlements_customer_product_id_fkey" FOREIGN KEY ("customer_product_id") REFERENCES "public"."customer_products"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "customer_entitlements" ADD CONSTRAINT "customer_entitlements_entitlement_id_fkey" FOREIGN KEY ("entitlement_id") REFERENCES "public"."entitlements"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "customer_prices" ADD CONSTRAINT "customer_prices_customer_product_id_fkey" FOREIGN KEY ("customer_product_id") REFERENCES "public"."customer_products"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "customer_prices" ADD CONSTRAINT "customer_prices_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "customer_prices" ADD CONSTRAINT "customer_prices_price_id_fkey" FOREIGN KEY ("price_id") REFERENCES "public"."prices"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "customer_products" ADD CONSTRAINT "customer_products_free_trial_id_fkey" FOREIGN KEY ("free_trial_id") REFERENCES "public"."free_trials"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "customer_products" ADD CONSTRAINT "customer_products_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "customer_products" ADD CONSTRAINT "customer_products_internal_product_id_fkey" FOREIGN KEY ("internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "customer_products" ADD CONSTRAINT "customer_products_internal_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "customers" ADD CONSTRAINT "customers_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "entities" ADD CONSTRAINT "entities_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "entities" ADD CONSTRAINT "entities_internal_feature_id_fkey" FOREIGN KEY ("internal_feature_id") REFERENCES "public"."features"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "entities" ADD CONSTRAINT "entities_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "entitlements" ADD CONSTRAINT "entitlements_internal_feature_id_fkey" FOREIGN KEY ("internal_feature_id") REFERENCES "public"."features"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "entitlements" ADD CONSTRAINT "entitlements_internal_product_id_fkey" FOREIGN KEY ("internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "events" ADD CONSTRAINT "events_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "features" ADD CONSTRAINT "features_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "free_trials" ADD CONSTRAINT "free_trials_internal_product_id_fkey" FOREIGN KEY ("internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "invitation" ADD CONSTRAINT "invitation_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "invitation" ADD CONSTRAINT "invitation_inviter_id_user_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "invoice_line_items" ADD CONSTRAINT "invoice_line_items_invoice_id_fkey" FOREIGN KEY ("invoice_id") REFERENCES "public"."invoices"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "invoices" ADD CONSTRAINT "invoices_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "invoices" ADD CONSTRAINT "invoices_internal_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "member" ADD CONSTRAINT "member_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "member" ADD CONSTRAINT "member_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "migration_errors" ADD CONSTRAINT "migration_customers_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "migration_errors" ADD CONSTRAINT "migration_customers_migration_job_id_fkey" FOREIGN KEY ("migration_job_id") REFERENCES "public"."migration_jobs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "migration_jobs" ADD CONSTRAINT "migration_jobs_from_internal_product_id_fkey" FOREIGN KEY ("from_internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "migration_jobs" ADD CONSTRAINT "migration_jobs_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "migration_jobs" ADD CONSTRAINT "migration_jobs_to_internal_product_id_fkey" FOREIGN KEY ("to_internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_session_id_session_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."session"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_refresh_id_oauth_refresh_token_id_fk" FOREIGN KEY ("refresh_id") REFERENCES "public"."oauth_refresh_token"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_client" ADD CONSTRAINT "oauth_client_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_consent" ADD CONSTRAINT "oauth_consent_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_consent" ADD CONSTRAINT "oauth_consent_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_session_id_session_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."session"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "prices" ADD CONSTRAINT "prices_entitlement_id_fkey" FOREIGN KEY ("entitlement_id") REFERENCES "public"."entitlements"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "prices" ADD CONSTRAINT "prices_internal_product_id_fkey" FOREIGN KEY ("internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "products" ADD CONSTRAINT "products_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "referral_codes" ADD CONSTRAINT "referral_codes_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "referral_codes" ADD CONSTRAINT "referral_codes_internal_reward_program_id_fkey" FOREIGN KEY ("internal_reward_program_id") REFERENCES "public"."reward_programs"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "referral_codes" ADD CONSTRAINT "referral_codes_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "replaceables" ADD CONSTRAINT "replaceables_cus_ent_id_fkey" FOREIGN KEY ("cus_ent_id") REFERENCES "public"."customer_entitlements"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "revenuecat_mappings" ADD CONSTRAINT "revenuecat_mappings_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reward_programs" ADD CONSTRAINT "reward_triggers_internal_reward_id_fkey" FOREIGN KEY ("internal_reward_id") REFERENCES "public"."rewards"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reward_programs" ADD CONSTRAINT "reward_triggers_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reward_redemptions" ADD CONSTRAINT "reward_redemptions_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reward_redemptions" ADD CONSTRAINT "reward_redemptions_internal_reward_program_id_fkey" FOREIGN KEY ("internal_reward_program_id") REFERENCES "public"."reward_programs"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reward_redemptions" ADD CONSTRAINT "reward_redemptions_referral_code_id_fkey" FOREIGN KEY ("referral_code_id") REFERENCES "public"."referral_codes"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "rewards" ADD CONSTRAINT "coupons_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "rollovers" ADD CONSTRAINT "rollover_cus_ent_id_fkey" FOREIGN KEY ("cus_ent_id") REFERENCES "public"."customer_entitlements"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint +ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "user" ADD CONSTRAINT "user_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "vercel_resources" ADD CONSTRAINT "vercel_resources_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "account_userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "idx_actions_on_internal_entity_id" ON "actions" USING btree ("internal_entity_id");--> statement-breakpoint +CREATE UNIQUE INDEX "auto_topup_limits_org_env_internal_customer_feature_unique" ON "auto_topup_limit_states" USING btree ("org_id","env","internal_customer_id","feature_id");--> statement-breakpoint +CREATE INDEX "idx_checkouts_stripe_invoice_id" ON "checkouts" USING btree ("stripe_invoice_id");--> statement-breakpoint +CREATE INDEX "idx_customer_entitlements_product_id" ON "customer_entitlements" USING btree ("customer_product_id");--> statement-breakpoint +CREATE INDEX "idx_customer_entitlements_internal_customer_id" ON "customer_entitlements" USING btree ("internal_customer_id");--> statement-breakpoint +CREATE INDEX "idx_customer_entitlements_entitlement_id" ON "customer_entitlements" USING btree ("entitlement_id");--> statement-breakpoint +CREATE INDEX "idx_customer_entitlements_loose_customer_expires" ON "customer_entitlements" USING btree ("internal_customer_id","expires_at") WHERE "customer_entitlements"."customer_product_id" IS NULL;--> statement-breakpoint +CREATE INDEX "idx_customer_prices_product_id" ON "customer_prices" USING btree ("customer_product_id");--> statement-breakpoint +CREATE INDEX "idx_customer_prices_price_id" ON "customer_prices" USING btree ("price_id");--> statement-breakpoint +CREATE INDEX "idx_customer_products_customer_status" ON "customer_products" USING btree ("internal_customer_id","status");--> statement-breakpoint +CREATE INDEX "idx_customer_products_on_internal_entity_id" ON "customer_products" USING btree ("internal_entity_id");--> statement-breakpoint +CREATE UNIQUE INDEX "customers_email_null_id_unique" ON "customers" USING btree ("org_id","env",lower("email")) WHERE "customers"."id" IS NULL AND "customers"."email" IS NOT NULL AND "customers"."email" != '';--> statement-breakpoint +CREATE INDEX "idx_customers_org_env_fingerprint" ON "customers" USING btree ("org_id","env","fingerprint") WHERE "customers"."fingerprint" IS NOT NULL;--> statement-breakpoint +CREATE INDEX "idx_entities_internal_customer_id" ON "entities" USING hash ("internal_customer_id");--> statement-breakpoint +CREATE INDEX "idx_entities_customer_internal_desc" ON "entities" USING btree ("internal_customer_id","internal_id" DESC);--> statement-breakpoint +CREATE INDEX "idx_entitlements_internal_product_id" ON "entitlements" USING btree ("internal_product_id");--> statement-breakpoint +CREATE INDEX "idx_events_internal_customer_id" ON "events" USING btree ("internal_customer_id");--> statement-breakpoint +CREATE INDEX "idx_events_internal_entity_id" ON "events" USING btree ("internal_entity_id");--> statement-breakpoint +CREATE INDEX "idx_events_customer_non_usage_ts" ON "events" USING btree ("internal_customer_id","timestamp" DESC,"id" DESC) WHERE "events"."set_usage" = false;--> statement-breakpoint +CREATE INDEX "idx_free_trials_internal_product_id" ON "free_trials" USING btree ("internal_product_id");--> statement-breakpoint +CREATE INDEX "invitation_organizationId_idx" ON "invitation" USING btree ("organization_id");--> statement-breakpoint +CREATE INDEX "invitation_email_idx" ON "invitation" USING btree ("email");--> statement-breakpoint +CREATE INDEX "idx_invoices_customer_created" ON "invoices" USING btree ("internal_customer_id","created_at" DESC,"id" DESC);--> statement-breakpoint +CREATE INDEX "member_organizationId_idx" ON "member" USING btree ("organization_id");--> statement-breakpoint +CREATE INDEX "member_userId_idx" ON "member" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "idx_prices_internal_product_id" ON "prices" USING btree ("internal_product_id");--> statement-breakpoint +CREATE INDEX "idx_prices_entitlement_id" ON "prices" USING btree ("entitlement_id");--> statement-breakpoint +CREATE INDEX "idx_replaceables_cus_ent_id" ON "replaceables" USING btree ("cus_ent_id");--> statement-breakpoint +CREATE INDEX "idx_rollovers_cus_ent_id" ON "rollovers" USING btree ("cus_ent_id");--> statement-breakpoint +CREATE INDEX "idx_rollovers_cus_ent_expires" ON "rollovers" USING btree ("cus_ent_id","expires_at");--> statement-breakpoint +CREATE INDEX "session_userId_idx" ON "session" USING btree ("user_id");--> statement-breakpoint +CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier"); \ No newline at end of file diff --git a/shared/drizzle/0001_fixed_whizzer.sql b/shared/drizzle/0001_fixed_whizzer.sql new file mode 100644 index 000000000..d0e9805f6 --- /dev/null +++ b/shared/drizzle/0001_fixed_whizzer.sql @@ -0,0 +1,2 @@ +ALTER TABLE "customers" ADD COLUMN "usage_alerts" jsonb;--> statement-breakpoint +ALTER TABLE "entities" ADD COLUMN "usage_alerts" jsonb; \ No newline at end of file diff --git a/shared/drizzle/meta/0000_snapshot.json b/shared/drizzle/meta/0000_snapshot.json new file mode 100644 index 000000000..c16a6aa01 --- /dev/null +++ b/shared/drizzle/meta/0000_snapshot.json @@ -0,0 +1,5264 @@ +{ + "id": "3798c672-9c46-4dd0-9465-fc1b3f6bdc1e", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.actions": { + "name": "actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_actions_on_internal_entity_id": { + "name": "idx_actions_on_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "actions_org_id_fkey": { + "name": "actions_org_id_fkey", + "tableFrom": "actions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "actions_customer_id_fkey": { + "name": "actions_customer_id_fkey", + "tableFrom": "actions", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "actions_entity_id_fkey": { + "name": "actions_entity_id_fkey", + "tableFrom": "actions", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hashed_key": { + "name": "hashed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_org_id_fkey": { + "name": "api_keys_org_id_fkey", + "tableFrom": "api_keys", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_keys_hashed_key_key": { + "name": "api_keys_hashed_key_key", + "nullsNotDistinct": false, + "columns": [ + "hashed_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auto_topup_limit_states": { + "name": "auto_topup_limit_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purchase_window_ends_at": { + "name": "purchase_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "purchase_count": { + "name": "purchase_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempt_window_ends_at": { + "name": "attempt_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed_attempt_window_ends_at": { + "name": "failed_attempt_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "failed_attempt_count": { + "name": "failed_attempt_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "last_failed_attempt_at": { + "name": "last_failed_attempt_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": { + "auto_topup_limits_org_env_internal_customer_feature_unique": { + "name": "auto_topup_limits_org_env_internal_customer_feature_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "feature_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_topup_limits_org_id_fkey": { + "name": "auto_topup_limits_org_id_fkey", + "tableFrom": "auto_topup_limit_states", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_topup_limits_internal_customer_id_fkey": { + "name": "auto_topup_limits_internal_customer_id_fkey", + "tableFrom": "auto_topup_limit_states", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_results": { + "name": "chat_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.checkouts": { + "name": "checkouts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "params_version": { + "name": "params_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_checkouts_stripe_invoice_id": { + "name": "idx_checkouts_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_entitlements": { + "name": "customer_entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "customer_product_id": { + "name": "customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "unlimited": { + "name": "unlimited", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "balance": { + "name": "balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "next_reset_at": { + "name": "next_reset_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "usage_allowed": { + "name": "usage_allowed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "adjustment": { + "name": "adjustment", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "additional_balance": { + "name": "additional_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "cache_version": { + "name": "cache_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_entitlements_product_id": { + "name": "idx_customer_entitlements_product_id", + "columns": [ + { + "expression": "customer_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_internal_customer_id": { + "name": "idx_customer_entitlements_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_entitlement_id": { + "name": "idx_customer_entitlements_entitlement_id", + "columns": [ + { + "expression": "entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_loose_customer_expires": { + "name": "idx_customer_entitlements_loose_customer_expires", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customer_entitlements\".\"customer_product_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entitlements_internal_feature_id_fkey": { + "name": "entitlements_internal_feature_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_entitlements_internal_entity_id_fkey": { + "name": "customer_entitlements_internal_entity_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_entitlements_customer_product_id_fkey": { + "name": "customer_entitlements_customer_product_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "customer_products", + "columnsFrom": [ + "customer_product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "customer_entitlements_entitlement_id_fkey": { + "name": "customer_entitlements_entitlement_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "entitlements", + "columnsFrom": [ + "entitlement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_prices": { + "name": "customer_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "price_id": { + "name": "price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_product_id": { + "name": "customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_prices_product_id": { + "name": "idx_customer_prices_product_id", + "columns": [ + { + "expression": "customer_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_prices_price_id": { + "name": "idx_customer_prices_price_id", + "columns": [ + { + "expression": "price_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_prices_customer_product_id_fkey": { + "name": "customer_prices_customer_product_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "customer_products", + "columnsFrom": [ + "customer_product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_prices_internal_customer_id_fkey": { + "name": "customer_prices_internal_customer_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_prices_price_id_fkey": { + "name": "customer_prices_price_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "prices", + "columnsFrom": [ + "price_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_products": { + "name": "customer_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "canceled": { + "name": "canceled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "starts_at": { + "name": "starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "free_trial_id": { + "name": "free_trial_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trial_ends_at": { + "name": "trial_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "collection_method": { + "name": "collection_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'charge_automatically'" + }, + "subscription_ids": { + "name": "subscription_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "scheduled_ids": { + "name": "scheduled_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_version": { + "name": "billing_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_version": { + "name": "api_version", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "api_semver": { + "name": "api_semver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_products_customer_status": { + "name": "idx_customer_products_customer_status", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_on_internal_entity_id": { + "name": "idx_customer_products_on_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_products_free_trial_id_fkey": { + "name": "customer_products_free_trial_id_fkey", + "tableFrom": "customer_products", + "tableTo": "free_trials", + "columnsFrom": [ + "free_trial_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "customer_products_internal_customer_id_fkey": { + "name": "customer_products_internal_customer_id_fkey", + "tableFrom": "customer_products", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "customer_products_internal_product_id_fkey": { + "name": "customer_products_internal_product_id_fkey", + "tableFrom": "customer_products", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "customer_products_internal_entity_id_fkey": { + "name": "customer_products_internal_entity_id_fkey", + "tableFrom": "customer_products", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customers": { + "name": "customers", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processors": { + "name": "processors", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "send_email_receipts": { + "name": "send_email_receipts", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "auto_topups": { + "name": "auto_topups", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "spend_limits": { + "name": "spend_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "customers_email_null_id_unique": { + "name": "customers_email_null_id_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"customers\".\"id\" IS NULL AND \"customers\".\"email\" IS NOT NULL AND \"customers\".\"email\" != ''", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_org_env_fingerprint": { + "name": "idx_customers_org_env_fingerprint", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"fingerprint\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customers_org_id_fkey": { + "name": "customers_org_id_fkey", + "tableFrom": "customers", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cus_id_constraint": { + "name": "cus_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.entities": { + "name": "entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spend_limits": { + "name": "spend_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_entities_internal_customer_id": { + "name": "idx_entities_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + }, + "idx_entities_customer_internal_desc": { + "name": "idx_entities_customer_internal_desc", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"internal_id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entities_internal_customer_id_fkey": { + "name": "entities_internal_customer_id_fkey", + "tableFrom": "entities", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entities_internal_feature_id_fkey": { + "name": "entities_internal_feature_id_fkey", + "tableFrom": "entities", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entities_org_id_fkey": { + "name": "entities_org_id_fkey", + "tableFrom": "entities", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entity_id_constraint": { + "name": "entity_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "env", + "internal_customer_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entitlements": { + "name": "entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "allowance_type": { + "name": "allowance_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowance": { + "name": "allowance", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "interval": { + "name": "interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interval_count": { + "name": "interval_count", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "carry_from_previous": { + "name": "carry_from_previous", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entity_feature_id": { + "name": "entity_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "rollover": { + "name": "rollover", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_entitlements_internal_product_id": { + "name": "idx_entitlements_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entitlements_internal_feature_id_fkey": { + "name": "entitlements_internal_feature_id_fkey", + "tableFrom": "entitlements", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entitlements_internal_product_id_fkey": { + "name": "entitlements_internal_product_id_fkey", + "tableFrom": "entitlements", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entitlements_id_key": { + "name": "entitlements_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_name": { + "name": "event_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "value": { + "name": "value", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "set_usage": { + "name": "set_usage", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_events_internal_customer_id": { + "name": "idx_events_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_events_internal_entity_id": { + "name": "idx_events_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_events_customer_non_usage_ts": { + "name": "idx_events_customer_non_usage_ts", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"timestamp\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"events\".\"set_usage\" = false", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "events_internal_customer_id_fkey": { + "name": "events_internal_customer_id_fkey", + "tableFrom": "events", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_event_constraint": { + "name": "unique_event_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "env", + "customer_id", + "event_name", + "idempotency_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.features": { + "name": "features", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "display": { + "name": "display", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "event_names": { + "name": "event_names", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "features_org_id_fkey": { + "name": "features_org_id_fkey", + "tableFrom": "features", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "feature_id_constraint": { + "name": "feature_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.free_trials": { + "name": "free_trials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'day'" + }, + "length": { + "name": "length", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "unique_fingerprint": { + "name": "unique_fingerprint", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "card_required": { + "name": "card_required", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": { + "idx_free_trials_internal_product_id": { + "name": "idx_free_trials_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "free_trials_internal_product_id_fkey": { + "name": "free_trials_internal_product_id_fkey", + "tableFrom": "free_trials", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_organization_id_organizations_id_fk": { + "name": "invitation_organization_id_organizations_id_fk", + "tableFrom": "invitation", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.invoice_line_items": { + "name": "invoice_line_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "invoice_id": { + "name": "invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_item_id": { + "name": "stripe_invoice_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_item_id": { + "name": "stripe_subscription_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_product_id": { + "name": "stripe_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_price_id": { + "name": "stripe_price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_discountable": { + "name": "stripe_discountable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "amount": { + "name": "amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "amount_after_discounts": { + "name": "amount_after_discounts", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "stripe_quantity": { + "name": "stripe_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "total_quantity": { + "name": "total_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "paid_quantity": { + "name": "paid_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_source": { + "name": "description_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "billing_timing": { + "name": "billing_timing", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prorated": { + "name": "prorated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "price_id": { + "name": "price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_product_ids": { + "name": "customer_product_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "customer_price_ids": { + "name": "customer_price_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "customer_entitlement_ids": { + "name": "customer_entitlement_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effective_period_start": { + "name": "effective_period_start", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "effective_period_end": { + "name": "effective_period_end", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "discounts": { + "name": "discounts", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "invoice_line_items_invoice_id_fkey": { + "name": "invoice_line_items_invoice_id_fkey", + "tableFrom": "invoice_line_items", + "tableTo": "invoices", + "columnsFrom": [ + "invoice_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoice_line_items_stripe_id_unique": { + "name": "invoice_line_items_stripe_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoices": { + "name": "invoices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "product_ids": { + "name": "product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "internal_product_ids": { + "name": "internal_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "hosted_invoice_url": { + "name": "hosted_invoice_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total": { + "name": "total", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "discounts": { + "name": "discounts", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "items": { + "name": "items", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": { + "idx_invoices_customer_created": { + "name": "idx_invoices_customer_created", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invoices_internal_customer_id_fkey": { + "name": "invoices_internal_customer_id_fkey", + "tableFrom": "invoices", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invoices_internal_entity_id_fkey": { + "name": "invoices_internal_entity_id_fkey", + "tableFrom": "invoices", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoices_stripe_id_key": { + "name": "invoices_stripe_id_key", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jwks": { + "name": "jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_organization_id_organizations_id_fk": { + "name": "member_organization_id_organizations_id_fk", + "tableFrom": "member", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.metadata": { + "name": "metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_checkout_session_id": { + "name": "stripe_checkout_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_errors": { + "name": "migration_errors", + "schema": "", + "columns": { + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_job_id": { + "name": "migration_job_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "migration_customers_internal_customer_id_fkey": { + "name": "migration_customers_internal_customer_id_fkey", + "tableFrom": "migration_errors", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_customers_migration_job_id_fkey": { + "name": "migration_customers_migration_job_id_fkey", + "tableFrom": "migration_errors", + "tableTo": "migration_jobs", + "columnsFrom": [ + "migration_job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "migration_errors_pkey": { + "name": "migration_errors_pkey", + "columns": [ + "internal_customer_id", + "migration_job_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_jobs": { + "name": "migration_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_step": { + "name": "current_step", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from_internal_product_id": { + "name": "from_internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to_internal_product_id": { + "name": "to_internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "step_details": { + "name": "step_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "migration_jobs_from_internal_product_id_fkey": { + "name": "migration_jobs_from_internal_product_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "products", + "columnsFrom": [ + "from_internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_jobs_org_id_fkey": { + "name": "migration_jobs_org_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_jobs_to_internal_product_id_fkey": { + "name": "migration_jobs_to_internal_product_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "products", + "columnsFrom": [ + "to_internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_refresh_token", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "name": "revoked", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_currency": { + "name": "default_currency", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'usd'" + }, + "stripe_connected": { + "name": "stripe_connected", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "stripe_config": { + "name": "stripe_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "test_stripe_connect": { + "name": "test_stripe_connect", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "live_stripe_connect": { + "name": "live_stripe_connect", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "processor_configs": { + "name": "processor_configs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "test_pkey": { + "name": "test_pkey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "live_pkey": { + "name": "live_pkey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "svix_config": { + "name": "svix_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "onboarded": { + "name": "onboarded", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "deployed": { + "name": "deployed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "organizations_test_pkey_key": { + "name": "organizations_test_pkey_key", + "nullsNotDistinct": false, + "columns": [ + "test_pkey" + ] + }, + "organizations_live_pkey_key": { + "name": "organizations_live_pkey_key", + "nullsNotDistinct": false, + "columns": [ + "live_pkey" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.prices": { + "name": "prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tier_behavior": { + "name": "tier_behavior", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "proration_config": { + "name": "proration_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + } + }, + "indexes": { + "idx_prices_internal_product_id": { + "name": "idx_prices_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_prices_entitlement_id": { + "name": "idx_prices_entitlement_id", + "columns": [ + { + "expression": "entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "prices_entitlement_id_fkey": { + "name": "prices_entitlement_id_fkey", + "tableFrom": "prices", + "tableTo": "entitlements", + "columnsFrom": [ + "entitlement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "prices_internal_product_id_fkey": { + "name": "prices_internal_product_id_fkey", + "tableFrom": "prices", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "prices_id_key": { + "name": "prices_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.products": { + "name": "products", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_add_on": { + "name": "is_add_on", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "group": { + "name": "group", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "version": { + "name": "version", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "base_variant_id": { + "name": "base_variant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "products_org_id_fkey": { + "name": "products_org_id_fkey", + "tableFrom": "products", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_product": { + "name": "unique_product", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env", + "version" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_codes": { + "name": "referral_codes", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_reward_program_id": { + "name": "internal_reward_program_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "referral_codes_internal_customer_id_fkey": { + "name": "referral_codes_internal_customer_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_codes_internal_reward_program_id_fkey": { + "name": "referral_codes_internal_reward_program_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "reward_programs", + "columnsFrom": [ + "internal_reward_program_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_codes_org_id_fkey": { + "name": "referral_codes_org_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "referral_codes_pkey": { + "name": "referral_codes_pkey", + "columns": [ + "code", + "org_id", + "env" + ] + } + }, + "uniqueConstraints": { + "referral_codes_id_key": { + "name": "referral_codes_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.replaceables": { + "name": "replaceables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cus_ent_id": { + "name": "cus_ent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "from_entity_id": { + "name": "from_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delete_next_cycle": { + "name": "delete_next_cycle", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "idx_replaceables_cus_ent_id": { + "name": "idx_replaceables_cus_ent_id", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "replaceables_cus_ent_id_fkey": { + "name": "replaceables_cus_ent_id_fkey", + "tableFrom": "replaceables", + "tableTo": "customer_entitlements", + "columnsFrom": [ + "cus_ent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.revenuecat_mappings": { + "name": "revenuecat_mappings", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autumn_product_id": { + "name": "autumn_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revenuecat_product_ids": { + "name": "revenuecat_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "revenuecat_mappings_org_id_fkey": { + "name": "revenuecat_mappings_org_id_fkey", + "tableFrom": "revenuecat_mappings", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "revenuecat_mappings_pkey": { + "name": "revenuecat_mappings_pkey", + "columns": [ + "org_id", + "env", + "autumn_product_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reward_programs": { + "name": "reward_programs", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "internal_reward_id": { + "name": "internal_reward_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_redemptions": { + "name": "max_redemptions", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "unlimited_redemptions": { + "name": "unlimited_redemptions", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "when": { + "name": "when", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'immediately'" + }, + "product_ids": { + "name": "product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{\"\"}'" + }, + "exclude_trial": { + "name": "exclude_trial", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "received_by": { + "name": "received_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "reward_triggers_internal_reward_id_fkey": { + "name": "reward_triggers_internal_reward_id_fkey", + "tableFrom": "reward_programs", + "tableTo": "rewards", + "columnsFrom": [ + "internal_reward_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_triggers_org_id_fkey": { + "name": "reward_triggers_org_id_fkey", + "tableFrom": "reward_programs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reward_redemptions": { + "name": "reward_redemptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "triggered": { + "name": "triggered", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "internal_reward_program_id": { + "name": "internal_reward_program_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied": { + "name": "applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "redeemer_applied": { + "name": "redeemer_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "referral_code_id": { + "name": "referral_code_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "reward_redemptions_internal_customer_id_fkey": { + "name": "reward_redemptions_internal_customer_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_redemptions_internal_reward_program_id_fkey": { + "name": "reward_redemptions_internal_reward_program_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "reward_programs", + "columnsFrom": [ + "internal_reward_program_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_redemptions_referral_code_id_fkey": { + "name": "reward_redemptions_referral_code_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "referral_codes", + "columnsFrom": [ + "referral_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rewards": { + "name": "rewards", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discount_config": { + "name": "discount_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "free_product_config": { + "name": "free_product_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "free_product_id": { + "name": "free_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promo_codes": { + "name": "promo_codes", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "coupons_org_id_fkey": { + "name": "coupons_org_id_fkey", + "tableFrom": "rewards", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rollovers": { + "name": "rollovers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cus_ent_id": { + "name": "cus_ent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "balance": { + "name": "balance", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "usage": { + "name": "usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_rollovers_cus_ent_id": { + "name": "idx_rollovers_cus_ent_id", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_rollovers_cus_ent_expires": { + "name": "idx_rollovers_cus_ent_expires", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "rollover_cus_ent_id_fkey": { + "name": "rollover_cus_ent_id_fkey", + "tableFrom": "rollovers", + "tableTo": "customer_entitlements", + "columnsFrom": [ + "cus_ent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "usage_features": { + "name": "usage_features", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_period_start": { + "name": "current_period_start", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_period_end": { + "name": "current_period_end", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "subscriptions_org_id_fkey": { + "name": "subscriptions_org_id_fkey", + "tableFrom": "subscriptions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "subscriptions_stripe_id_key": { + "name": "subscriptions_stripe_id_key", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_created_by_fkey": { + "name": "user_created_by_fkey", + "tableFrom": "user", + "tableTo": "organizations", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vercel_resources": { + "name": "vercel_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "vercel_resources_org_id_fkey": { + "name": "vercel_resources_org_id_fkey", + "tableFrom": "vercel_resources", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/shared/drizzle/meta/0001_snapshot.json b/shared/drizzle/meta/0001_snapshot.json new file mode 100644 index 000000000..12c4fe29c --- /dev/null +++ b/shared/drizzle/meta/0001_snapshot.json @@ -0,0 +1,5276 @@ +{ + "id": "80d9d3d7-381f-4594-826d-ecb87b92a096", + "prevId": "3798c672-9c46-4dd0-9465-fc1b3f6bdc1e", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.actions": { + "name": "actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_actions_on_internal_entity_id": { + "name": "idx_actions_on_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "actions_org_id_fkey": { + "name": "actions_org_id_fkey", + "tableFrom": "actions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "actions_customer_id_fkey": { + "name": "actions_customer_id_fkey", + "tableFrom": "actions", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "actions_entity_id_fkey": { + "name": "actions_entity_id_fkey", + "tableFrom": "actions", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hashed_key": { + "name": "hashed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_org_id_fkey": { + "name": "api_keys_org_id_fkey", + "tableFrom": "api_keys", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_keys_hashed_key_key": { + "name": "api_keys_hashed_key_key", + "nullsNotDistinct": false, + "columns": [ + "hashed_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auto_topup_limit_states": { + "name": "auto_topup_limit_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purchase_window_ends_at": { + "name": "purchase_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "purchase_count": { + "name": "purchase_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "attempt_window_ends_at": { + "name": "attempt_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed_attempt_window_ends_at": { + "name": "failed_attempt_window_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "failed_attempt_count": { + "name": "failed_attempt_count", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "last_failed_attempt_at": { + "name": "last_failed_attempt_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + } + }, + "indexes": { + "auto_topup_limits_org_env_internal_customer_feature_unique": { + "name": "auto_topup_limits_org_env_internal_customer_feature_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "feature_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_topup_limits_org_id_fkey": { + "name": "auto_topup_limits_org_id_fkey", + "tableFrom": "auto_topup_limit_states", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_topup_limits_internal_customer_id_fkey": { + "name": "auto_topup_limits_internal_customer_id_fkey", + "tableFrom": "auto_topup_limit_states", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_results": { + "name": "chat_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.checkouts": { + "name": "checkouts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "params_version": { + "name": "params_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_checkouts_stripe_invoice_id": { + "name": "idx_checkouts_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_entitlements": { + "name": "customer_entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "customer_product_id": { + "name": "customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text COLLATE \"C\"", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "unlimited": { + "name": "unlimited", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "balance": { + "name": "balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "next_reset_at": { + "name": "next_reset_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "usage_allowed": { + "name": "usage_allowed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "adjustment": { + "name": "adjustment", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "additional_balance": { + "name": "additional_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "cache_version": { + "name": "cache_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_entitlements_product_id": { + "name": "idx_customer_entitlements_product_id", + "columns": [ + { + "expression": "customer_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_internal_customer_id": { + "name": "idx_customer_entitlements_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_entitlement_id": { + "name": "idx_customer_entitlements_entitlement_id", + "columns": [ + { + "expression": "entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_entitlements_loose_customer_expires": { + "name": "idx_customer_entitlements_loose_customer_expires", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customer_entitlements\".\"customer_product_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entitlements_internal_feature_id_fkey": { + "name": "entitlements_internal_feature_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_entitlements_internal_entity_id_fkey": { + "name": "customer_entitlements_internal_entity_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_entitlements_customer_product_id_fkey": { + "name": "customer_entitlements_customer_product_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "customer_products", + "columnsFrom": [ + "customer_product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "customer_entitlements_entitlement_id_fkey": { + "name": "customer_entitlements_entitlement_id_fkey", + "tableFrom": "customer_entitlements", + "tableTo": "entitlements", + "columnsFrom": [ + "entitlement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_prices": { + "name": "customer_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "price_id": { + "name": "price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_product_id": { + "name": "customer_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_prices_product_id": { + "name": "idx_customer_prices_product_id", + "columns": [ + { + "expression": "customer_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_prices_price_id": { + "name": "idx_customer_prices_price_id", + "columns": [ + { + "expression": "price_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_prices_customer_product_id_fkey": { + "name": "customer_prices_customer_product_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "customer_products", + "columnsFrom": [ + "customer_product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_prices_internal_customer_id_fkey": { + "name": "customer_prices_internal_customer_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "customer_prices_price_id_fkey": { + "name": "customer_prices_price_id_fkey", + "tableFrom": "customer_prices", + "tableTo": "prices", + "columnsFrom": [ + "price_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customer_products": { + "name": "customer_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "canceled": { + "name": "canceled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "starts_at": { + "name": "starts_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "free_trial_id": { + "name": "free_trial_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trial_ends_at": { + "name": "trial_ends_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "collection_method": { + "name": "collection_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'charge_automatically'" + }, + "subscription_ids": { + "name": "subscription_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "scheduled_ids": { + "name": "scheduled_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_version": { + "name": "billing_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_version": { + "name": "api_version", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "api_semver": { + "name": "api_semver", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_customer_products_customer_status": { + "name": "idx_customer_products_customer_status", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customer_products_on_internal_entity_id": { + "name": "idx_customer_products_on_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customer_products_free_trial_id_fkey": { + "name": "customer_products_free_trial_id_fkey", + "tableFrom": "customer_products", + "tableTo": "free_trials", + "columnsFrom": [ + "free_trial_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "customer_products_internal_customer_id_fkey": { + "name": "customer_products_internal_customer_id_fkey", + "tableFrom": "customer_products", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "customer_products_internal_product_id_fkey": { + "name": "customer_products_internal_product_id_fkey", + "tableFrom": "customer_products", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "customer_products_internal_entity_id_fkey": { + "name": "customer_products_internal_entity_id_fkey", + "tableFrom": "customer_products", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.customers": { + "name": "customers", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processors": { + "name": "processors", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "send_email_receipts": { + "name": "send_email_receipts", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "auto_topups": { + "name": "auto_topups", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "spend_limits": { + "name": "spend_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_alerts": { + "name": "usage_alerts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "customers_email_null_id_unique": { + "name": "customers_email_null_id_unique", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"customers\".\"id\" IS NULL AND \"customers\".\"email\" IS NOT NULL AND \"customers\".\"email\" != ''", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_customers_org_env_fingerprint": { + "name": "idx_customers_org_env_fingerprint", + "columns": [ + { + "expression": "org_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"customers\".\"fingerprint\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "customers_org_id_fkey": { + "name": "customers_org_id_fkey", + "tableFrom": "customers", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cus_id_constraint": { + "name": "cus_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.entities": { + "name": "entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spend_limits": { + "name": "spend_limits", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_alerts": { + "name": "usage_alerts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_entities_internal_customer_id": { + "name": "idx_entities_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + }, + "idx_entities_customer_internal_desc": { + "name": "idx_entities_customer_internal_desc", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"internal_id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entities_internal_customer_id_fkey": { + "name": "entities_internal_customer_id_fkey", + "tableFrom": "entities", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entities_internal_feature_id_fkey": { + "name": "entities_internal_feature_id_fkey", + "tableFrom": "entities", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entities_org_id_fkey": { + "name": "entities_org_id_fkey", + "tableFrom": "entities", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entity_id_constraint": { + "name": "entity_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "env", + "internal_customer_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entitlements": { + "name": "entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "allowance_type": { + "name": "allowance_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowance": { + "name": "allowance", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "interval": { + "name": "interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "interval_count": { + "name": "interval_count", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "carry_from_previous": { + "name": "carry_from_previous", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entity_feature_id": { + "name": "entity_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "rollover": { + "name": "rollover", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_entitlements_internal_product_id": { + "name": "idx_entitlements_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entitlements_internal_feature_id_fkey": { + "name": "entitlements_internal_feature_id_fkey", + "tableFrom": "entitlements", + "tableTo": "features", + "columnsFrom": [ + "internal_feature_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entitlements_internal_product_id_fkey": { + "name": "entitlements_internal_product_id_fkey", + "tableFrom": "entitlements", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entitlements_id_key": { + "name": "entitlements_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_slug": { + "name": "org_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_name": { + "name": "event_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "value": { + "name": "value", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "set_usage": { + "name": "set_usage", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_events_internal_customer_id": { + "name": "idx_events_internal_customer_id", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_events_internal_entity_id": { + "name": "idx_events_internal_entity_id", + "columns": [ + { + "expression": "internal_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_events_customer_non_usage_ts": { + "name": "idx_events_customer_non_usage_ts", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"timestamp\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"events\".\"set_usage\" = false", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "events_internal_customer_id_fkey": { + "name": "events_internal_customer_id_fkey", + "tableFrom": "events", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_event_constraint": { + "name": "unique_event_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "env", + "customer_id", + "event_name", + "idempotency_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.features": { + "name": "features", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "display": { + "name": "display", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "event_names": { + "name": "event_names", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "features_org_id_fkey": { + "name": "features_org_id_fkey", + "tableFrom": "features", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "feature_id_constraint": { + "name": "feature_id_constraint", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.free_trials": { + "name": "free_trials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'day'" + }, + "length": { + "name": "length", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "unique_fingerprint": { + "name": "unique_fingerprint", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "card_required": { + "name": "card_required", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": { + "idx_free_trials_internal_product_id": { + "name": "idx_free_trials_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "free_trials_internal_product_id_fkey": { + "name": "free_trials_internal_product_id_fkey", + "tableFrom": "free_trials", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_organization_id_organizations_id_fk": { + "name": "invitation_organization_id_organizations_id_fk", + "tableFrom": "invitation", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.invoice_line_items": { + "name": "invoice_line_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "invoice_id": { + "name": "invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_item_id": { + "name": "stripe_invoice_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_item_id": { + "name": "stripe_subscription_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_product_id": { + "name": "stripe_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_price_id": { + "name": "stripe_price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_discountable": { + "name": "stripe_discountable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "amount": { + "name": "amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "amount_after_discounts": { + "name": "amount_after_discounts", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "stripe_quantity": { + "name": "stripe_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "total_quantity": { + "name": "total_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "paid_quantity": { + "name": "paid_quantity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_source": { + "name": "description_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "billing_timing": { + "name": "billing_timing", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prorated": { + "name": "prorated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "price_id": { + "name": "price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_product_ids": { + "name": "customer_product_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "customer_price_ids": { + "name": "customer_price_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "customer_entitlement_ids": { + "name": "customer_entitlement_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_feature_id": { + "name": "internal_feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effective_period_start": { + "name": "effective_period_start", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "effective_period_end": { + "name": "effective_period_end", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "discounts": { + "name": "discounts", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "invoice_line_items_invoice_id_fkey": { + "name": "invoice_line_items_invoice_id_fkey", + "tableFrom": "invoice_line_items", + "tableTo": "invoices", + "columnsFrom": [ + "invoice_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoice_line_items_stripe_id_unique": { + "name": "invoice_line_items_stripe_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invoices": { + "name": "invoices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "product_ids": { + "name": "product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "internal_product_ids": { + "name": "internal_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_entity_id": { + "name": "internal_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "hosted_invoice_url": { + "name": "hosted_invoice_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total": { + "name": "total", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "discounts": { + "name": "discounts", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "items": { + "name": "items", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": { + "idx_invoices_customer_created": { + "name": "idx_invoices_customer_created", + "columns": [ + { + "expression": "internal_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invoices_internal_customer_id_fkey": { + "name": "invoices_internal_customer_id_fkey", + "tableFrom": "invoices", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invoices_internal_entity_id_fkey": { + "name": "invoices_internal_entity_id_fkey", + "tableFrom": "invoices", + "tableTo": "entities", + "columnsFrom": [ + "internal_entity_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invoices_stripe_id_key": { + "name": "invoices_stripe_id_key", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jwks": { + "name": "jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_organization_id_organizations_id_fk": { + "name": "member_organization_id_organizations_id_fk", + "tableFrom": "member", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.metadata": { + "name": "metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_checkout_session_id": { + "name": "stripe_checkout_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_errors": { + "name": "migration_errors", + "schema": "", + "columns": { + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_job_id": { + "name": "migration_job_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "migration_customers_internal_customer_id_fkey": { + "name": "migration_customers_internal_customer_id_fkey", + "tableFrom": "migration_errors", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_customers_migration_job_id_fkey": { + "name": "migration_customers_migration_job_id_fkey", + "tableFrom": "migration_errors", + "tableTo": "migration_jobs", + "columnsFrom": [ + "migration_job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "migration_errors_pkey": { + "name": "migration_errors_pkey", + "columns": [ + "internal_customer_id", + "migration_job_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_jobs": { + "name": "migration_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_step": { + "name": "current_step", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from_internal_product_id": { + "name": "from_internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to_internal_product_id": { + "name": "to_internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "step_details": { + "name": "step_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "migration_jobs_from_internal_product_id_fkey": { + "name": "migration_jobs_from_internal_product_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "products", + "columnsFrom": [ + "from_internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_jobs_org_id_fkey": { + "name": "migration_jobs_org_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_jobs_to_internal_product_id_fkey": { + "name": "migration_jobs_to_internal_product_id_fkey", + "tableFrom": "migration_jobs", + "tableTo": "products", + "columnsFrom": [ + "to_internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_refresh_token", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "name": "revoked", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_currency": { + "name": "default_currency", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'usd'" + }, + "stripe_connected": { + "name": "stripe_connected", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "stripe_config": { + "name": "stripe_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "test_stripe_connect": { + "name": "test_stripe_connect", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "live_stripe_connect": { + "name": "live_stripe_connect", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "processor_configs": { + "name": "processor_configs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "test_pkey": { + "name": "test_pkey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "live_pkey": { + "name": "live_pkey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "svix_config": { + "name": "svix_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "onboarded": { + "name": "onboarded", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "deployed": { + "name": "deployed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "organizations_test_pkey_key": { + "name": "organizations_test_pkey_key", + "nullsNotDistinct": false, + "columns": [ + "test_pkey" + ] + }, + "organizations_live_pkey_key": { + "name": "organizations_live_pkey_key", + "nullsNotDistinct": false, + "columns": [ + "live_pkey" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.prices": { + "name": "prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text COLLATE \"C\"", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_product_id": { + "name": "internal_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tier_behavior": { + "name": "tier_behavior", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "is_custom": { + "name": "is_custom", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "proration_config": { + "name": "proration_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + } + }, + "indexes": { + "idx_prices_internal_product_id": { + "name": "idx_prices_internal_product_id", + "columns": [ + { + "expression": "internal_product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_prices_entitlement_id": { + "name": "idx_prices_entitlement_id", + "columns": [ + { + "expression": "entitlement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "prices_entitlement_id_fkey": { + "name": "prices_entitlement_id_fkey", + "tableFrom": "prices", + "tableTo": "entitlements", + "columnsFrom": [ + "entitlement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "prices_internal_product_id_fkey": { + "name": "prices_internal_product_id_fkey", + "tableFrom": "prices", + "tableTo": "products", + "columnsFrom": [ + "internal_product_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "prices_id_key": { + "name": "prices_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.products": { + "name": "products", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_add_on": { + "name": "is_add_on", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "group": { + "name": "group", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "version": { + "name": "version", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "processor": { + "name": "processor", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "base_variant_id": { + "name": "base_variant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "products_org_id_fkey": { + "name": "products_org_id_fkey", + "tableFrom": "products", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_product": { + "name": "unique_product", + "nullsNotDistinct": false, + "columns": [ + "org_id", + "id", + "env", + "version" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_codes": { + "name": "referral_codes", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "internal_reward_program_id": { + "name": "internal_reward_program_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "referral_codes_internal_customer_id_fkey": { + "name": "referral_codes_internal_customer_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_codes_internal_reward_program_id_fkey": { + "name": "referral_codes_internal_reward_program_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "reward_programs", + "columnsFrom": [ + "internal_reward_program_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "referral_codes_org_id_fkey": { + "name": "referral_codes_org_id_fkey", + "tableFrom": "referral_codes", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "referral_codes_pkey": { + "name": "referral_codes_pkey", + "columns": [ + "code", + "org_id", + "env" + ] + } + }, + "uniqueConstraints": { + "referral_codes_id_key": { + "name": "referral_codes_id_key", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.replaceables": { + "name": "replaceables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cus_ent_id": { + "name": "cus_ent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "from_entity_id": { + "name": "from_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delete_next_cycle": { + "name": "delete_next_cycle", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "idx_replaceables_cus_ent_id": { + "name": "idx_replaceables_cus_ent_id", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "replaceables_cus_ent_id_fkey": { + "name": "replaceables_cus_ent_id_fkey", + "tableFrom": "replaceables", + "tableTo": "customer_entitlements", + "columnsFrom": [ + "cus_ent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.revenuecat_mappings": { + "name": "revenuecat_mappings", + "schema": "", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "autumn_product_id": { + "name": "autumn_product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revenuecat_product_ids": { + "name": "revenuecat_product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "revenuecat_mappings_org_id_fkey": { + "name": "revenuecat_mappings_org_id_fkey", + "tableFrom": "revenuecat_mappings", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "revenuecat_mappings_pkey": { + "name": "revenuecat_mappings_pkey", + "columns": [ + "org_id", + "env", + "autumn_product_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reward_programs": { + "name": "reward_programs", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "internal_reward_id": { + "name": "internal_reward_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_redemptions": { + "name": "max_redemptions", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "unlimited_redemptions": { + "name": "unlimited_redemptions", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "when": { + "name": "when", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'immediately'" + }, + "product_ids": { + "name": "product_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{\"\"}'" + }, + "exclude_trial": { + "name": "exclude_trial", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "received_by": { + "name": "received_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "reward_triggers_internal_reward_id_fkey": { + "name": "reward_triggers_internal_reward_id_fkey", + "tableFrom": "reward_programs", + "tableTo": "rewards", + "columnsFrom": [ + "internal_reward_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_triggers_org_id_fkey": { + "name": "reward_triggers_org_id_fkey", + "tableFrom": "reward_programs", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reward_redemptions": { + "name": "reward_redemptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "internal_customer_id": { + "name": "internal_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "triggered": { + "name": "triggered", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "internal_reward_program_id": { + "name": "internal_reward_program_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied": { + "name": "applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "redeemer_applied": { + "name": "redeemer_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "referral_code_id": { + "name": "referral_code_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "reward_redemptions_internal_customer_id_fkey": { + "name": "reward_redemptions_internal_customer_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "customers", + "columnsFrom": [ + "internal_customer_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_redemptions_internal_reward_program_id_fkey": { + "name": "reward_redemptions_internal_reward_program_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "reward_programs", + "columnsFrom": [ + "internal_reward_program_id" + ], + "columnsTo": [ + "internal_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reward_redemptions_referral_code_id_fkey": { + "name": "reward_redemptions_referral_code_id_fkey", + "tableFrom": "reward_redemptions", + "tableTo": "referral_codes", + "columnsFrom": [ + "referral_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rewards": { + "name": "rewards", + "schema": "", + "columns": { + "internal_id": { + "name": "internal_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discount_config": { + "name": "discount_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "free_product_config": { + "name": "free_product_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "free_product_id": { + "name": "free_product_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "promo_codes": { + "name": "promo_codes", + "type": "jsonb[]", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "coupons_org_id_fkey": { + "name": "coupons_org_id_fkey", + "tableFrom": "rewards", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rollovers": { + "name": "rollovers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cus_ent_id": { + "name": "cus_ent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "balance": { + "name": "balance", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "usage": { + "name": "usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "entities": { + "name": "entities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "idx_rollovers_cus_ent_id": { + "name": "idx_rollovers_cus_ent_id", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_rollovers_cus_ent_expires": { + "name": "idx_rollovers_cus_ent_expires", + "columns": [ + { + "expression": "cus_ent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "rollover_cus_ent_id_fkey": { + "name": "rollover_cus_ent_id_fkey", + "tableFrom": "rollovers", + "tableTo": "customer_entitlements", + "columnsFrom": [ + "cus_ent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "usage_features": { + "name": "usage_features", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_period_start": { + "name": "current_period_start", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "current_period_end": { + "name": "current_period_end", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "subscriptions_org_id_fkey": { + "name": "subscriptions_org_id_fkey", + "tableFrom": "subscriptions", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "subscriptions_stripe_id_key": { + "name": "subscriptions_stripe_id_key", + "nullsNotDistinct": false, + "columns": [ + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_created_by_fkey": { + "name": "user_created_by_fkey", + "tableFrom": "user", + "tableTo": "organizations", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vercel_resources": { + "name": "vercel_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "vercel_resources_org_id_fkey": { + "name": "vercel_resources_org_id_fkey", + "tableFrom": "vercel_resources", + "tableTo": "organizations", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/shared/drizzle/meta/_journal.json b/shared/drizzle/meta/_journal.json new file mode 100644 index 000000000..a07fa4e42 --- /dev/null +++ b/shared/drizzle/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1773773937035, + "tag": "0000_rainy_siren", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1773916904198, + "tag": "0001_fixed_whizzer", + "breakpoints": true + } + ] +} \ No newline at end of file From 870665b2e4bb158111bb70f9149ead4f8028fc26 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 10:59:34 +0000 Subject: [PATCH 03/49] Remove gitignored drizzle migration files from tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These files are in .gitignore and should not be committed — migrations are generated locally via db:push. Co-Authored-By: Claude Opus 4.6 --- shared/drizzle/0000_rainy_siren.sql | 746 ---- shared/drizzle/0001_fixed_whizzer.sql | 2 - shared/drizzle/meta/0000_snapshot.json | 5264 ----------------------- shared/drizzle/meta/0001_snapshot.json | 5276 ------------------------ shared/drizzle/meta/_journal.json | 20 - 5 files changed, 11308 deletions(-) delete mode 100644 shared/drizzle/0000_rainy_siren.sql delete mode 100644 shared/drizzle/0001_fixed_whizzer.sql delete mode 100644 shared/drizzle/meta/0000_snapshot.json delete mode 100644 shared/drizzle/meta/0001_snapshot.json delete mode 100644 shared/drizzle/meta/_journal.json diff --git a/shared/drizzle/0000_rainy_siren.sql b/shared/drizzle/0000_rainy_siren.sql deleted file mode 100644 index c6c036cf2..000000000 --- a/shared/drizzle/0000_rainy_siren.sql +++ /dev/null @@ -1,746 +0,0 @@ -CREATE TABLE "account" ( - "id" text PRIMARY KEY NOT NULL, - "account_id" text NOT NULL, - "provider_id" text NOT NULL, - "user_id" text NOT NULL, - "access_token" text, - "refresh_token" text, - "id_token" text, - "access_token_expires_at" timestamp, - "refresh_token_expires_at" timestamp, - "scope" text, - "password" text, - "created_at" timestamp with time zone NOT NULL, - "updated_at" timestamp with time zone NOT NULL -); ---> statement-breakpoint -ALTER TABLE "account" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "actions" ( - "id" text COLLATE "C" PRIMARY KEY NOT NULL, - "request_id" text NOT NULL, - "org_id" text NOT NULL, - "org_slug" text NOT NULL, - "env" text NOT NULL, - "customer_id" text, - "internal_customer_id" text, - "entity_id" text, - "internal_entity_id" text, - "type" text NOT NULL, - "auth_type" text NOT NULL, - "method" text NOT NULL, - "path" text NOT NULL, - "timestamp" timestamp with time zone DEFAULT now() NOT NULL, - "properties" jsonb -); ---> statement-breakpoint -ALTER TABLE "actions" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "api_keys" ( - "id" text COLLATE "C" PRIMARY KEY NOT NULL, - "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, - "name" text, - "prefix" text, - "org_id" text, - "user_id" text, - "env" text, - "hashed_key" text, - "meta" jsonb, - CONSTRAINT "api_keys_hashed_key_key" UNIQUE("hashed_key") -); ---> statement-breakpoint -CREATE TABLE "auto_topup_limit_states" ( - "id" text PRIMARY KEY NOT NULL, - "org_id" text NOT NULL, - "env" text NOT NULL, - "internal_customer_id" text NOT NULL, - "customer_id" text NOT NULL, - "feature_id" text NOT NULL, - "purchase_window_ends_at" numeric NOT NULL, - "purchase_count" numeric DEFAULT 0 NOT NULL, - "attempt_window_ends_at" numeric NOT NULL, - "attempt_count" numeric DEFAULT 0 NOT NULL, - "failed_attempt_window_ends_at" numeric NOT NULL, - "failed_attempt_count" numeric DEFAULT 0 NOT NULL, - "last_attempt_at" numeric, - "last_failed_attempt_at" numeric, - "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, - "updated_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL -); ---> statement-breakpoint -CREATE TABLE "chat_results" ( - "id" text COLLATE "C" PRIMARY KEY NOT NULL, - "created_at" numeric, - "data" jsonb NOT NULL -); ---> statement-breakpoint -ALTER TABLE "chat_results" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "checkouts" ( - "id" text PRIMARY KEY NOT NULL, - "org_id" text NOT NULL, - "env" text NOT NULL, - "internal_customer_id" text NOT NULL, - "customer_id" text NOT NULL, - "action" text NOT NULL, - "params" jsonb NOT NULL, - "params_version" integer DEFAULT 0 NOT NULL, - "status" text DEFAULT 'pending' NOT NULL, - "response" jsonb, - "stripe_invoice_id" text, - "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, - "expires_at" numeric NOT NULL, - "completed_at" numeric -); ---> statement-breakpoint -CREATE TABLE "customer_entitlements" ( - "id" text COLLATE "C" PRIMARY KEY NOT NULL, - "customer_product_id" text, - "entitlement_id" text NOT NULL, - "internal_customer_id" text COLLATE "C" NOT NULL, - "internal_entity_id" text, - "internal_feature_id" text NOT NULL, - "unlimited" boolean DEFAULT false, - "balance" numeric DEFAULT 0 NOT NULL, - "created_at" numeric NOT NULL, - "next_reset_at" numeric, - "usage_allowed" boolean DEFAULT false, - "adjustment" numeric, - "additional_balance" numeric DEFAULT 0 NOT NULL, - "entities" jsonb, - "expires_at" numeric, - "cache_version" integer DEFAULT 0, - "customer_id" text, - "feature_id" text, - "external_id" text -); ---> statement-breakpoint -CREATE TABLE "customer_prices" ( - "id" text PRIMARY KEY NOT NULL, - "created_at" numeric NOT NULL, - "price_id" text, - "options" jsonb, - "internal_customer_id" text, - "customer_product_id" text -); ---> statement-breakpoint -CREATE TABLE "customer_products" ( - "id" text COLLATE "C" PRIMARY KEY NOT NULL, - "internal_customer_id" text NOT NULL, - "internal_product_id" text NOT NULL, - "internal_entity_id" text, - "created_at" numeric, - "status" text, - "processor" jsonb, - "canceled" boolean DEFAULT false, - "canceled_at" numeric, - "ended_at" numeric, - "starts_at" numeric, - "options" jsonb[], - "product_id" text, - "free_trial_id" text, - "trial_ends_at" numeric, - "collection_method" text DEFAULT 'charge_automatically', - "subscription_ids" text[], - "scheduled_ids" text[], - "quantity" numeric DEFAULT 1, - "is_custom" boolean DEFAULT false NOT NULL, - "customer_id" text, - "entity_id" text, - "billing_version" text, - "api_version" numeric, - "api_semver" text, - "external_id" text -); ---> statement-breakpoint -CREATE TABLE "customers" ( - "internal_id" text COLLATE "C" PRIMARY KEY NOT NULL, - "org_id" text NOT NULL, - "created_at" numeric NOT NULL, - "name" text, - "id" text, - "email" text, - "fingerprint" text DEFAULT null, - "metadata" jsonb, - "env" text NOT NULL, - "processor" jsonb, - "processors" jsonb DEFAULT '{}'::jsonb, - "send_email_receipts" boolean DEFAULT false, - "auto_topups" jsonb, - "spend_limits" jsonb, - CONSTRAINT "cus_id_constraint" UNIQUE("org_id","id","env") -); ---> statement-breakpoint -ALTER TABLE "customers" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "entities" ( - "id" text, - "org_id" text, - "created_at" numeric NOT NULL, - "internal_id" text PRIMARY KEY NOT NULL, - "internal_customer_id" text NOT NULL, - "env" text, - "name" text, - "deleted" boolean DEFAULT false NOT NULL, - "internal_feature_id" text, - "spend_limits" jsonb, - "feature_id" text, - CONSTRAINT "entity_id_constraint" UNIQUE("org_id","env","internal_customer_id","id") -); ---> statement-breakpoint -CREATE TABLE "entitlements" ( - "id" text PRIMARY KEY NOT NULL, - "created_at" numeric NOT NULL, - "internal_feature_id" text NOT NULL, - "internal_product_id" text, - "is_custom" boolean DEFAULT false, - "allowance_type" text, - "allowance" numeric, - "interval" text, - "interval_count" numeric DEFAULT 1, - "carry_from_previous" boolean DEFAULT false, - "entity_feature_id" text DEFAULT null, - "org_id" text, - "feature_id" text, - "usage_limit" numeric, - "rollover" jsonb, - CONSTRAINT "entitlements_id_key" UNIQUE("id") -); ---> statement-breakpoint -CREATE TABLE "events" ( - "id" text PRIMARY KEY NOT NULL, - "org_id" text NOT NULL, - "org_slug" text NOT NULL, - "internal_customer_id" text, - "env" text NOT NULL, - "created_at" bigint, - "timestamp" timestamp with time zone, - "event_name" text NOT NULL, - "idempotency_key" text DEFAULT null, - "value" numeric, - "set_usage" boolean DEFAULT false, - "entity_id" text, - "internal_entity_id" text, - "customer_id" text NOT NULL, - "properties" jsonb, - CONSTRAINT "unique_event_constraint" UNIQUE("org_id","env","customer_id","event_name","idempotency_key") -); ---> statement-breakpoint -CREATE TABLE "features" ( - "internal_id" text COLLATE "C" PRIMARY KEY NOT NULL, - "org_id" text NOT NULL, - "created_at" numeric, - "env" text, - "id" text NOT NULL, - "name" text, - "type" text NOT NULL, - "config" jsonb, - "display" jsonb DEFAULT null, - "archived" boolean DEFAULT false NOT NULL, - "event_names" text[] DEFAULT '{}', - CONSTRAINT "feature_id_constraint" UNIQUE("org_id","id","env") -); ---> statement-breakpoint -CREATE TABLE "free_trials" ( - "id" text PRIMARY KEY NOT NULL, - "created_at" numeric NOT NULL, - "internal_product_id" text, - "duration" text DEFAULT 'day', - "length" numeric, - "unique_fingerprint" boolean, - "is_custom" boolean DEFAULT false, - "card_required" boolean DEFAULT false -); ---> statement-breakpoint -CREATE TABLE "invitation" ( - "id" text PRIMARY KEY NOT NULL, - "organization_id" text NOT NULL, - "email" text NOT NULL, - "role" text, - "status" text DEFAULT 'pending' NOT NULL, - "created_at" timestamp with time zone NOT NULL, - "expires_at" timestamp with time zone NOT NULL, - "inviter_id" text NOT NULL -); ---> statement-breakpoint -ALTER TABLE "invitation" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "invoice_line_items" ( - "id" text COLLATE "C" PRIMARY KEY NOT NULL, - "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, - "invoice_id" text, - "stripe_id" text, - "stripe_invoice_id" text, - "stripe_invoice_item_id" text, - "stripe_subscription_item_id" text, - "stripe_product_id" text, - "stripe_price_id" text, - "stripe_discountable" boolean DEFAULT true NOT NULL, - "amount" numeric NOT NULL, - "amount_after_discounts" numeric NOT NULL, - "currency" text DEFAULT 'usd' NOT NULL, - "stripe_quantity" numeric, - "total_quantity" numeric, - "paid_quantity" numeric, - "description" text NOT NULL, - "description_source" text, - "direction" text NOT NULL, - "billing_timing" text, - "prorated" boolean DEFAULT false NOT NULL, - "price_id" text, - "customer_product_ids" jsonb DEFAULT '[]'::jsonb NOT NULL, - "customer_price_ids" jsonb DEFAULT '[]'::jsonb NOT NULL, - "customer_entitlement_ids" jsonb DEFAULT '[]'::jsonb NOT NULL, - "internal_product_id" text, - "product_id" text, - "internal_feature_id" text, - "feature_id" text, - "effective_period_start" numeric, - "effective_period_end" numeric, - "discounts" jsonb[] DEFAULT '{}', - CONSTRAINT "invoice_line_items_stripe_id_unique" UNIQUE("stripe_id") -); ---> statement-breakpoint -CREATE TABLE "invoices" ( - "id" text COLLATE "C" PRIMARY KEY NOT NULL, - "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, - "product_ids" text[] DEFAULT '{}', - "internal_product_ids" text[] DEFAULT '{}', - "internal_customer_id" text NOT NULL, - "internal_entity_id" text, - "stripe_id" text NOT NULL, - "status" text DEFAULT 'draft' NOT NULL, - "hosted_invoice_url" text, - "total" numeric DEFAULT 0 NOT NULL, - "currency" text DEFAULT 'usd' NOT NULL, - "discounts" jsonb[] DEFAULT '{}', - "items" jsonb[] DEFAULT '{}', - CONSTRAINT "invoices_stripe_id_key" UNIQUE("stripe_id") -); ---> statement-breakpoint -CREATE TABLE "jwks" ( - "id" text PRIMARY KEY NOT NULL, - "public_key" text NOT NULL, - "private_key" text NOT NULL, - "created_at" timestamp with time zone NOT NULL, - "expires_at" timestamp with time zone -); ---> statement-breakpoint -ALTER TABLE "jwks" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "member" ( - "id" text PRIMARY KEY NOT NULL, - "organization_id" text NOT NULL, - "user_id" text NOT NULL, - "role" text DEFAULT 'member' NOT NULL, - "created_at" timestamp with time zone NOT NULL -); ---> statement-breakpoint -ALTER TABLE "member" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "metadata" ( - "id" text PRIMARY KEY NOT NULL, - "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, - "expires_at" numeric, - "data" jsonb, - "type" text, - "stripe_invoice_id" text, - "stripe_checkout_session_id" text -); ---> statement-breakpoint -CREATE TABLE "migration_errors" ( - "internal_customer_id" text NOT NULL, - "migration_job_id" text NOT NULL, - "created_at" numeric, - "updated_at" numeric, - "data" jsonb, - "message" text, - "code" text, - CONSTRAINT "migration_errors_pkey" PRIMARY KEY("internal_customer_id","migration_job_id") -); ---> statement-breakpoint -CREATE TABLE "migration_jobs" ( - "id" text PRIMARY KEY NOT NULL, - "org_id" text NOT NULL, - "env" text NOT NULL, - "created_at" numeric NOT NULL, - "updated_at" numeric, - "current_step" text, - "from_internal_product_id" text, - "to_internal_product_id" text, - "step_details" jsonb -); ---> statement-breakpoint -CREATE TABLE "oauth_access_token" ( - "id" text PRIMARY KEY NOT NULL, - "token" text, - "client_id" text NOT NULL, - "session_id" text, - "user_id" text, - "reference_id" text, - "refresh_id" text, - "expires_at" timestamp with time zone, - "created_at" timestamp with time zone, - "scopes" text[] NOT NULL, - CONSTRAINT "oauth_access_token_token_unique" UNIQUE("token") -); ---> statement-breakpoint -ALTER TABLE "oauth_access_token" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "oauth_client" ( - "id" text PRIMARY KEY NOT NULL, - "client_id" text NOT NULL, - "client_secret" text, - "disabled" boolean DEFAULT false, - "skip_consent" boolean, - "enable_end_session" boolean, - "scopes" text[], - "user_id" text, - "created_at" timestamp with time zone, - "updated_at" timestamp with time zone, - "name" text, - "uri" text, - "icon" text, - "contacts" text[], - "tos" text, - "policy" text, - "software_id" text, - "software_version" text, - "software_statement" text, - "redirect_uris" text[] NOT NULL, - "post_logout_redirect_uris" text[], - "token_endpoint_auth_method" text, - "grant_types" text[], - "response_types" text[], - "public" boolean, - "type" text, - "reference_id" text, - "metadata" jsonb, - CONSTRAINT "oauth_client_client_id_unique" UNIQUE("client_id") -); ---> statement-breakpoint -ALTER TABLE "oauth_client" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "oauth_consent" ( - "id" text PRIMARY KEY NOT NULL, - "client_id" text NOT NULL, - "user_id" text, - "reference_id" text, - "scopes" text[] NOT NULL, - "created_at" timestamp with time zone, - "updated_at" timestamp with time zone -); ---> statement-breakpoint -ALTER TABLE "oauth_consent" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "oauth_refresh_token" ( - "id" text PRIMARY KEY NOT NULL, - "token" text NOT NULL, - "client_id" text NOT NULL, - "session_id" text, - "user_id" text NOT NULL, - "reference_id" text, - "expires_at" timestamp with time zone, - "created_at" timestamp with time zone, - "revoked" timestamp with time zone, - "scopes" text[] NOT NULL -); ---> statement-breakpoint -ALTER TABLE "oauth_refresh_token" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "organizations" ( - "id" text PRIMARY KEY NOT NULL, - "slug" text NOT NULL, - "name" text NOT NULL, - "logo" text, - "createdAt" timestamp with time zone NOT NULL, - "metadata" text, - "default_currency" text DEFAULT 'usd', - "stripe_connected" boolean DEFAULT false, - "stripe_config" jsonb, - "test_stripe_connect" jsonb DEFAULT '{}'::jsonb, - "live_stripe_connect" jsonb DEFAULT '{}'::jsonb, - "processor_configs" jsonb, - "test_pkey" text, - "live_pkey" text, - "svix_config" jsonb DEFAULT '{}'::jsonb, - "created_at" numeric, - "config" jsonb DEFAULT '{}'::jsonb NOT NULL, - "created_by" text, - "onboarded" boolean DEFAULT false, - "deployed" boolean DEFAULT false, - CONSTRAINT "organizations_slug_unique" UNIQUE("slug"), - CONSTRAINT "organizations_test_pkey_key" UNIQUE("test_pkey"), - CONSTRAINT "organizations_live_pkey_key" UNIQUE("live_pkey") -); ---> statement-breakpoint -CREATE TABLE "prices" ( - "id" text COLLATE "C" PRIMARY KEY NOT NULL, - "org_id" text NOT NULL, - "internal_product_id" text NOT NULL, - "config" jsonb, - "created_at" numeric NOT NULL, - "billing_type" text, - "tier_behavior" text DEFAULT null, - "is_custom" boolean DEFAULT false, - "entitlement_id" text DEFAULT null, - "proration_config" jsonb DEFAULT null, - CONSTRAINT "prices_id_key" UNIQUE("id") -); ---> statement-breakpoint -CREATE TABLE "products" ( - "internal_id" text PRIMARY KEY NOT NULL, - "id" text NOT NULL, - "name" text, - "description" text, - "org_id" text NOT NULL, - "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, - "env" text NOT NULL, - "is_add_on" boolean DEFAULT false NOT NULL, - "is_default" boolean DEFAULT false NOT NULL, - "group" text DEFAULT '', - "version" numeric DEFAULT 1 NOT NULL, - "processor" jsonb DEFAULT null, - "base_variant_id" text, - "archived" boolean DEFAULT false NOT NULL, - CONSTRAINT "unique_product" UNIQUE("org_id","id","env","version") -); ---> statement-breakpoint -CREATE TABLE "referral_codes" ( - "code" text NOT NULL, - "org_id" text NOT NULL, - "env" text NOT NULL, - "internal_customer_id" text, - "internal_reward_program_id" text, - "id" text NOT NULL, - "created_at" numeric, - CONSTRAINT "referral_codes_pkey" PRIMARY KEY("code","org_id","env"), - CONSTRAINT "referral_codes_id_key" UNIQUE("id") -); ---> statement-breakpoint -CREATE TABLE "replaceables" ( - "id" text PRIMARY KEY NOT NULL, - "cus_ent_id" text NOT NULL, - "created_at" bigint NOT NULL, - "from_entity_id" text, - "delete_next_cycle" boolean DEFAULT false NOT NULL -); ---> statement-breakpoint -ALTER TABLE "replaceables" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "revenuecat_mappings" ( - "org_id" text NOT NULL, - "env" text NOT NULL, - "autumn_product_id" text NOT NULL, - "revenuecat_product_ids" text[] DEFAULT '{}' NOT NULL, - CONSTRAINT "revenuecat_mappings_pkey" PRIMARY KEY("org_id","env","autumn_product_id") -); ---> statement-breakpoint -CREATE TABLE "reward_programs" ( - "internal_id" text PRIMARY KEY NOT NULL, - "id" text, - "created_at" numeric, - "internal_reward_id" text, - "max_redemptions" numeric, - "unlimited_redemptions" boolean DEFAULT false, - "org_id" text, - "env" text, - "when" text DEFAULT 'immediately', - "product_ids" text[] DEFAULT '{""}', - "exclude_trial" boolean DEFAULT false, - "received_by" text -); ---> statement-breakpoint -CREATE TABLE "reward_redemptions" ( - "id" text PRIMARY KEY NOT NULL, - "created_at" numeric, - "updated_at" numeric, - "internal_customer_id" text, - "triggered" boolean, - "internal_reward_program_id" text, - "applied" boolean DEFAULT false, - "redeemer_applied" boolean DEFAULT false, - "referral_code_id" text -); ---> statement-breakpoint -CREATE TABLE "rewards" ( - "internal_id" text PRIMARY KEY NOT NULL, - "id" text, - "org_id" text, - "env" text, - "created_at" numeric, - "name" text, - "discount_config" jsonb, - "free_product_config" jsonb, - "free_product_id" text, - "promo_codes" jsonb[], - "type" text -); ---> statement-breakpoint -CREATE TABLE "rollovers" ( - "id" text PRIMARY KEY NOT NULL, - "cus_ent_id" text NOT NULL, - "balance" numeric NOT NULL, - "expires_at" numeric, - "usage" numeric DEFAULT 0 NOT NULL, - "entities" jsonb DEFAULT '{}'::jsonb NOT NULL -); ---> statement-breakpoint -ALTER TABLE "rollovers" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "session" ( - "id" text PRIMARY KEY NOT NULL, - "expires_at" timestamp with time zone NOT NULL, - "token" text NOT NULL, - "created_at" timestamp with time zone NOT NULL, - "updated_at" timestamp with time zone NOT NULL, - "ip_address" text, - "user_agent" text, - "user_id" text NOT NULL, - "impersonated_by" text, - "active_organization_id" text, - "city" text, - "country" text, - CONSTRAINT "session_token_unique" UNIQUE("token") -); ---> statement-breakpoint -ALTER TABLE "session" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -CREATE TABLE "subscriptions" ( - "id" text PRIMARY KEY NOT NULL, - "org_id" text NOT NULL, - "stripe_id" text, - "stripe_schedule_id" text, - "created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL, - "metadata" jsonb DEFAULT '{}'::jsonb, - "usage_features" text[] DEFAULT '{}', - "env" text, - "current_period_start" numeric, - "current_period_end" numeric, - CONSTRAINT "subscriptions_stripe_id_key" UNIQUE("stripe_id") -); ---> statement-breakpoint -CREATE TABLE "user" ( - "id" text PRIMARY KEY NOT NULL, - "name" text NOT NULL, - "email" text NOT NULL, - "email_verified" boolean NOT NULL, - "image" text, - "created_at" timestamp with time zone NOT NULL, - "updated_at" timestamp with time zone NOT NULL, - "role" text, - "banned" boolean, - "ban_reason" text, - "ban_expires" timestamp with time zone, - "created_by" text, - "last_active_at" timestamp with time zone, - CONSTRAINT "user_email_unique" UNIQUE("email") -); ---> statement-breakpoint -CREATE TABLE "vercel_resources" ( - "id" text PRIMARY KEY NOT NULL, - "org_id" text NOT NULL, - "env" text NOT NULL, - "installation_id" text NOT NULL, - "name" text NOT NULL, - "status" text NOT NULL, - "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL -); ---> statement-breakpoint -CREATE TABLE "verification" ( - "id" text PRIMARY KEY NOT NULL, - "identifier" text NOT NULL, - "value" text NOT NULL, - "expires_at" timestamp with time zone NOT NULL, - "created_at" timestamp with time zone, - "updated_at" timestamp with time zone -); ---> statement-breakpoint -ALTER TABLE "verification" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint -ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "actions" ADD CONSTRAINT "actions_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "actions" ADD CONSTRAINT "actions_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "actions" ADD CONSTRAINT "actions_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "auto_topup_limit_states" ADD CONSTRAINT "auto_topup_limits_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "auto_topup_limit_states" ADD CONSTRAINT "auto_topup_limits_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "customer_entitlements" ADD CONSTRAINT "entitlements_internal_feature_id_fkey" FOREIGN KEY ("internal_feature_id") REFERENCES "public"."features"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "customer_entitlements" ADD CONSTRAINT "customer_entitlements_internal_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "customer_entitlements" ADD CONSTRAINT "customer_entitlements_customer_product_id_fkey" FOREIGN KEY ("customer_product_id") REFERENCES "public"."customer_products"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "customer_entitlements" ADD CONSTRAINT "customer_entitlements_entitlement_id_fkey" FOREIGN KEY ("entitlement_id") REFERENCES "public"."entitlements"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "customer_prices" ADD CONSTRAINT "customer_prices_customer_product_id_fkey" FOREIGN KEY ("customer_product_id") REFERENCES "public"."customer_products"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "customer_prices" ADD CONSTRAINT "customer_prices_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "customer_prices" ADD CONSTRAINT "customer_prices_price_id_fkey" FOREIGN KEY ("price_id") REFERENCES "public"."prices"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "customer_products" ADD CONSTRAINT "customer_products_free_trial_id_fkey" FOREIGN KEY ("free_trial_id") REFERENCES "public"."free_trials"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "customer_products" ADD CONSTRAINT "customer_products_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "customer_products" ADD CONSTRAINT "customer_products_internal_product_id_fkey" FOREIGN KEY ("internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "customer_products" ADD CONSTRAINT "customer_products_internal_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "customers" ADD CONSTRAINT "customers_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "entities" ADD CONSTRAINT "entities_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "entities" ADD CONSTRAINT "entities_internal_feature_id_fkey" FOREIGN KEY ("internal_feature_id") REFERENCES "public"."features"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "entities" ADD CONSTRAINT "entities_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "entitlements" ADD CONSTRAINT "entitlements_internal_feature_id_fkey" FOREIGN KEY ("internal_feature_id") REFERENCES "public"."features"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "entitlements" ADD CONSTRAINT "entitlements_internal_product_id_fkey" FOREIGN KEY ("internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "events" ADD CONSTRAINT "events_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "features" ADD CONSTRAINT "features_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "free_trials" ADD CONSTRAINT "free_trials_internal_product_id_fkey" FOREIGN KEY ("internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "invitation" ADD CONSTRAINT "invitation_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "invitation" ADD CONSTRAINT "invitation_inviter_id_user_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "invoice_line_items" ADD CONSTRAINT "invoice_line_items_invoice_id_fkey" FOREIGN KEY ("invoice_id") REFERENCES "public"."invoices"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "invoices" ADD CONSTRAINT "invoices_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "invoices" ADD CONSTRAINT "invoices_internal_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "member" ADD CONSTRAINT "member_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "member" ADD CONSTRAINT "member_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "migration_errors" ADD CONSTRAINT "migration_customers_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "migration_errors" ADD CONSTRAINT "migration_customers_migration_job_id_fkey" FOREIGN KEY ("migration_job_id") REFERENCES "public"."migration_jobs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "migration_jobs" ADD CONSTRAINT "migration_jobs_from_internal_product_id_fkey" FOREIGN KEY ("from_internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "migration_jobs" ADD CONSTRAINT "migration_jobs_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "migration_jobs" ADD CONSTRAINT "migration_jobs_to_internal_product_id_fkey" FOREIGN KEY ("to_internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_session_id_session_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."session"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_refresh_id_oauth_refresh_token_id_fk" FOREIGN KEY ("refresh_id") REFERENCES "public"."oauth_refresh_token"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "oauth_client" ADD CONSTRAINT "oauth_client_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "oauth_consent" ADD CONSTRAINT "oauth_consent_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "oauth_consent" ADD CONSTRAINT "oauth_consent_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_session_id_session_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."session"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "prices" ADD CONSTRAINT "prices_entitlement_id_fkey" FOREIGN KEY ("entitlement_id") REFERENCES "public"."entitlements"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "prices" ADD CONSTRAINT "prices_internal_product_id_fkey" FOREIGN KEY ("internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "products" ADD CONSTRAINT "products_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "referral_codes" ADD CONSTRAINT "referral_codes_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "referral_codes" ADD CONSTRAINT "referral_codes_internal_reward_program_id_fkey" FOREIGN KEY ("internal_reward_program_id") REFERENCES "public"."reward_programs"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "referral_codes" ADD CONSTRAINT "referral_codes_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "replaceables" ADD CONSTRAINT "replaceables_cus_ent_id_fkey" FOREIGN KEY ("cus_ent_id") REFERENCES "public"."customer_entitlements"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "revenuecat_mappings" ADD CONSTRAINT "revenuecat_mappings_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "reward_programs" ADD CONSTRAINT "reward_triggers_internal_reward_id_fkey" FOREIGN KEY ("internal_reward_id") REFERENCES "public"."rewards"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "reward_programs" ADD CONSTRAINT "reward_triggers_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "reward_redemptions" ADD CONSTRAINT "reward_redemptions_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "reward_redemptions" ADD CONSTRAINT "reward_redemptions_internal_reward_program_id_fkey" FOREIGN KEY ("internal_reward_program_id") REFERENCES "public"."reward_programs"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "reward_redemptions" ADD CONSTRAINT "reward_redemptions_referral_code_id_fkey" FOREIGN KEY ("referral_code_id") REFERENCES "public"."referral_codes"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "rewards" ADD CONSTRAINT "coupons_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "rollovers" ADD CONSTRAINT "rollover_cus_ent_id_fkey" FOREIGN KEY ("cus_ent_id") REFERENCES "public"."customer_entitlements"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint -ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "user" ADD CONSTRAINT "user_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "vercel_resources" ADD CONSTRAINT "vercel_resources_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "account_userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint -CREATE INDEX "idx_actions_on_internal_entity_id" ON "actions" USING btree ("internal_entity_id");--> statement-breakpoint -CREATE UNIQUE INDEX "auto_topup_limits_org_env_internal_customer_feature_unique" ON "auto_topup_limit_states" USING btree ("org_id","env","internal_customer_id","feature_id");--> statement-breakpoint -CREATE INDEX "idx_checkouts_stripe_invoice_id" ON "checkouts" USING btree ("stripe_invoice_id");--> statement-breakpoint -CREATE INDEX "idx_customer_entitlements_product_id" ON "customer_entitlements" USING btree ("customer_product_id");--> statement-breakpoint -CREATE INDEX "idx_customer_entitlements_internal_customer_id" ON "customer_entitlements" USING btree ("internal_customer_id");--> statement-breakpoint -CREATE INDEX "idx_customer_entitlements_entitlement_id" ON "customer_entitlements" USING btree ("entitlement_id");--> statement-breakpoint -CREATE INDEX "idx_customer_entitlements_loose_customer_expires" ON "customer_entitlements" USING btree ("internal_customer_id","expires_at") WHERE "customer_entitlements"."customer_product_id" IS NULL;--> statement-breakpoint -CREATE INDEX "idx_customer_prices_product_id" ON "customer_prices" USING btree ("customer_product_id");--> statement-breakpoint -CREATE INDEX "idx_customer_prices_price_id" ON "customer_prices" USING btree ("price_id");--> statement-breakpoint -CREATE INDEX "idx_customer_products_customer_status" ON "customer_products" USING btree ("internal_customer_id","status");--> statement-breakpoint -CREATE INDEX "idx_customer_products_on_internal_entity_id" ON "customer_products" USING btree ("internal_entity_id");--> statement-breakpoint -CREATE UNIQUE INDEX "customers_email_null_id_unique" ON "customers" USING btree ("org_id","env",lower("email")) WHERE "customers"."id" IS NULL AND "customers"."email" IS NOT NULL AND "customers"."email" != '';--> statement-breakpoint -CREATE INDEX "idx_customers_org_env_fingerprint" ON "customers" USING btree ("org_id","env","fingerprint") WHERE "customers"."fingerprint" IS NOT NULL;--> statement-breakpoint -CREATE INDEX "idx_entities_internal_customer_id" ON "entities" USING hash ("internal_customer_id");--> statement-breakpoint -CREATE INDEX "idx_entities_customer_internal_desc" ON "entities" USING btree ("internal_customer_id","internal_id" DESC);--> statement-breakpoint -CREATE INDEX "idx_entitlements_internal_product_id" ON "entitlements" USING btree ("internal_product_id");--> statement-breakpoint -CREATE INDEX "idx_events_internal_customer_id" ON "events" USING btree ("internal_customer_id");--> statement-breakpoint -CREATE INDEX "idx_events_internal_entity_id" ON "events" USING btree ("internal_entity_id");--> statement-breakpoint -CREATE INDEX "idx_events_customer_non_usage_ts" ON "events" USING btree ("internal_customer_id","timestamp" DESC,"id" DESC) WHERE "events"."set_usage" = false;--> statement-breakpoint -CREATE INDEX "idx_free_trials_internal_product_id" ON "free_trials" USING btree ("internal_product_id");--> statement-breakpoint -CREATE INDEX "invitation_organizationId_idx" ON "invitation" USING btree ("organization_id");--> statement-breakpoint -CREATE INDEX "invitation_email_idx" ON "invitation" USING btree ("email");--> statement-breakpoint -CREATE INDEX "idx_invoices_customer_created" ON "invoices" USING btree ("internal_customer_id","created_at" DESC,"id" DESC);--> statement-breakpoint -CREATE INDEX "member_organizationId_idx" ON "member" USING btree ("organization_id");--> statement-breakpoint -CREATE INDEX "member_userId_idx" ON "member" USING btree ("user_id");--> statement-breakpoint -CREATE INDEX "idx_prices_internal_product_id" ON "prices" USING btree ("internal_product_id");--> statement-breakpoint -CREATE INDEX "idx_prices_entitlement_id" ON "prices" USING btree ("entitlement_id");--> statement-breakpoint -CREATE INDEX "idx_replaceables_cus_ent_id" ON "replaceables" USING btree ("cus_ent_id");--> statement-breakpoint -CREATE INDEX "idx_rollovers_cus_ent_id" ON "rollovers" USING btree ("cus_ent_id");--> statement-breakpoint -CREATE INDEX "idx_rollovers_cus_ent_expires" ON "rollovers" USING btree ("cus_ent_id","expires_at");--> statement-breakpoint -CREATE INDEX "session_userId_idx" ON "session" USING btree ("user_id");--> statement-breakpoint -CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier"); \ No newline at end of file diff --git a/shared/drizzle/0001_fixed_whizzer.sql b/shared/drizzle/0001_fixed_whizzer.sql deleted file mode 100644 index d0e9805f6..000000000 --- a/shared/drizzle/0001_fixed_whizzer.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE "customers" ADD COLUMN "usage_alerts" jsonb;--> statement-breakpoint -ALTER TABLE "entities" ADD COLUMN "usage_alerts" jsonb; \ No newline at end of file diff --git a/shared/drizzle/meta/0000_snapshot.json b/shared/drizzle/meta/0000_snapshot.json deleted file mode 100644 index c16a6aa01..000000000 --- a/shared/drizzle/meta/0000_snapshot.json +++ /dev/null @@ -1,5264 +0,0 @@ -{ - "id": "3798c672-9c46-4dd0-9465-fc1b3f6bdc1e", - "prevId": "00000000-0000-0000-0000-000000000000", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "account_id": { - "name": "account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token_expires_at": { - "name": "access_token_expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "refresh_token_expires_at": { - "name": "refresh_token_expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "account_userId_idx": { - "name": "account_userId_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "account_user_id_user_id_fk": { - "name": "account_user_id_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.actions": { - "name": "actions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "request_id": { - "name": "request_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "org_slug": { - "name": "org_slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "entity_id": { - "name": "entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "internal_entity_id": { - "name": "internal_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "auth_type": { - "name": "auth_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "method": { - "name": "method", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "timestamp": { - "name": "timestamp", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "properties": { - "name": "properties", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_actions_on_internal_entity_id": { - "name": "idx_actions_on_internal_entity_id", - "columns": [ - { - "expression": "internal_entity_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "actions_org_id_fkey": { - "name": "actions_org_id_fkey", - "tableFrom": "actions", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "actions_customer_id_fkey": { - "name": "actions_customer_id_fkey", - "tableFrom": "actions", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "actions_entity_id_fkey": { - "name": "actions_entity_id_fkey", - "tableFrom": "actions", - "tableTo": "entities", - "columnsFrom": [ - "internal_entity_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.api_keys": { - "name": "api_keys", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "prefix": { - "name": "prefix", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "hashed_key": { - "name": "hashed_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "meta": { - "name": "meta", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "api_keys_org_id_fkey": { - "name": "api_keys_org_id_fkey", - "tableFrom": "api_keys", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_keys_hashed_key_key": { - "name": "api_keys_hashed_key_key", - "nullsNotDistinct": false, - "columns": [ - "hashed_key" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.auto_topup_limit_states": { - "name": "auto_topup_limit_states", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "feature_id": { - "name": "feature_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "purchase_window_ends_at": { - "name": "purchase_window_ends_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "purchase_count": { - "name": "purchase_count", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "attempt_window_ends_at": { - "name": "attempt_window_ends_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "attempt_count": { - "name": "attempt_count", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "failed_attempt_window_ends_at": { - "name": "failed_attempt_window_ends_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "failed_attempt_count": { - "name": "failed_attempt_count", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "last_attempt_at": { - "name": "last_attempt_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "last_failed_attempt_at": { - "name": "last_failed_attempt_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "updated_at": { - "name": "updated_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - } - }, - "indexes": { - "auto_topup_limits_org_env_internal_customer_feature_unique": { - "name": "auto_topup_limits_org_env_internal_customer_feature_unique", - "columns": [ - { - "expression": "org_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "env", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "feature_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "auto_topup_limits_org_id_fkey": { - "name": "auto_topup_limits_org_id_fkey", - "tableFrom": "auto_topup_limit_states", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "auto_topup_limits_internal_customer_id_fkey": { - "name": "auto_topup_limits_internal_customer_id_fkey", - "tableFrom": "auto_topup_limit_states", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.chat_results": { - "name": "chat_results", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "data": { - "name": "data", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.checkouts": { - "name": "checkouts", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "params": { - "name": "params", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "params_version": { - "name": "params_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "response": { - "name": "response", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "stripe_invoice_id": { - "name": "stripe_invoice_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "expires_at": { - "name": "expires_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "completed_at": { - "name": "completed_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_checkouts_stripe_invoice_id": { - "name": "idx_checkouts_stripe_invoice_id", - "columns": [ - { - "expression": "stripe_invoice_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.customer_entitlements": { - "name": "customer_entitlements", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "customer_product_id": { - "name": "customer_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "entitlement_id": { - "name": "entitlement_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text COLLATE \"C\"", - "primaryKey": false, - "notNull": true - }, - "internal_entity_id": { - "name": "internal_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "internal_feature_id": { - "name": "internal_feature_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "unlimited": { - "name": "unlimited", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "balance": { - "name": "balance", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "next_reset_at": { - "name": "next_reset_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "usage_allowed": { - "name": "usage_allowed", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "adjustment": { - "name": "adjustment", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "additional_balance": { - "name": "additional_balance", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "entities": { - "name": "entities", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "cache_version": { - "name": "cache_version", - "type": "integer", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "feature_id": { - "name": "feature_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "external_id": { - "name": "external_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_customer_entitlements_product_id": { - "name": "idx_customer_entitlements_product_id", - "columns": [ - { - "expression": "customer_product_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_customer_entitlements_internal_customer_id": { - "name": "idx_customer_entitlements_internal_customer_id", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_customer_entitlements_entitlement_id": { - "name": "idx_customer_entitlements_entitlement_id", - "columns": [ - { - "expression": "entitlement_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_customer_entitlements_loose_customer_expires": { - "name": "idx_customer_entitlements_loose_customer_expires", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "expires_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"customer_entitlements\".\"customer_product_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "entitlements_internal_feature_id_fkey": { - "name": "entitlements_internal_feature_id_fkey", - "tableFrom": "customer_entitlements", - "tableTo": "features", - "columnsFrom": [ - "internal_feature_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "customer_entitlements_internal_entity_id_fkey": { - "name": "customer_entitlements_internal_entity_id_fkey", - "tableFrom": "customer_entitlements", - "tableTo": "entities", - "columnsFrom": [ - "internal_entity_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "customer_entitlements_customer_product_id_fkey": { - "name": "customer_entitlements_customer_product_id_fkey", - "tableFrom": "customer_entitlements", - "tableTo": "customer_products", - "columnsFrom": [ - "customer_product_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "customer_entitlements_entitlement_id_fkey": { - "name": "customer_entitlements_entitlement_id_fkey", - "tableFrom": "customer_entitlements", - "tableTo": "entitlements", - "columnsFrom": [ - "entitlement_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.customer_prices": { - "name": "customer_prices", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "price_id": { - "name": "price_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "options": { - "name": "options", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "customer_product_id": { - "name": "customer_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_customer_prices_product_id": { - "name": "idx_customer_prices_product_id", - "columns": [ - { - "expression": "customer_product_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_customer_prices_price_id": { - "name": "idx_customer_prices_price_id", - "columns": [ - { - "expression": "price_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "customer_prices_customer_product_id_fkey": { - "name": "customer_prices_customer_product_id_fkey", - "tableFrom": "customer_prices", - "tableTo": "customer_products", - "columnsFrom": [ - "customer_product_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "customer_prices_internal_customer_id_fkey": { - "name": "customer_prices_internal_customer_id_fkey", - "tableFrom": "customer_prices", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "customer_prices_price_id_fkey": { - "name": "customer_prices_price_id_fkey", - "tableFrom": "customer_prices", - "tableTo": "prices", - "columnsFrom": [ - "price_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.customer_products": { - "name": "customer_products", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_product_id": { - "name": "internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_entity_id": { - "name": "internal_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "processor": { - "name": "processor", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "canceled": { - "name": "canceled", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "canceled_at": { - "name": "canceled_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "ended_at": { - "name": "ended_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "starts_at": { - "name": "starts_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "options": { - "name": "options", - "type": "jsonb[]", - "primaryKey": false, - "notNull": false - }, - "product_id": { - "name": "product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "free_trial_id": { - "name": "free_trial_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "trial_ends_at": { - "name": "trial_ends_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "collection_method": { - "name": "collection_method", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'charge_automatically'" - }, - "subscription_ids": { - "name": "subscription_ids", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "scheduled_ids": { - "name": "scheduled_ids", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "quantity": { - "name": "quantity", - "type": "numeric", - "primaryKey": false, - "notNull": false, - "default": 1 - }, - "is_custom": { - "name": "is_custom", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "entity_id": { - "name": "entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "billing_version": { - "name": "billing_version", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "api_version": { - "name": "api_version", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "api_semver": { - "name": "api_semver", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "external_id": { - "name": "external_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_customer_products_customer_status": { - "name": "idx_customer_products_customer_status", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_customer_products_on_internal_entity_id": { - "name": "idx_customer_products_on_internal_entity_id", - "columns": [ - { - "expression": "internal_entity_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "customer_products_free_trial_id_fkey": { - "name": "customer_products_free_trial_id_fkey", - "tableFrom": "customer_products", - "tableTo": "free_trials", - "columnsFrom": [ - "free_trial_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "customer_products_internal_customer_id_fkey": { - "name": "customer_products_internal_customer_id_fkey", - "tableFrom": "customer_products", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "customer_products_internal_product_id_fkey": { - "name": "customer_products_internal_product_id_fkey", - "tableFrom": "customer_products", - "tableTo": "products", - "columnsFrom": [ - "internal_product_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "customer_products_internal_entity_id_fkey": { - "name": "customer_products_internal_entity_id_fkey", - "tableFrom": "customer_products", - "tableTo": "entities", - "columnsFrom": [ - "internal_entity_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.customers": { - "name": "customers", - "schema": "", - "columns": { - "internal_id": { - "name": "internal_id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "processor": { - "name": "processor", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "processors": { - "name": "processors", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'::jsonb" - }, - "send_email_receipts": { - "name": "send_email_receipts", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "auto_topups": { - "name": "auto_topups", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "spend_limits": { - "name": "spend_limits", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "customers_email_null_id_unique": { - "name": "customers_email_null_id_unique", - "columns": [ - { - "expression": "org_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "env", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "lower(\"email\")", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"customers\".\"id\" IS NULL AND \"customers\".\"email\" IS NOT NULL AND \"customers\".\"email\" != ''", - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_customers_org_env_fingerprint": { - "name": "idx_customers_org_env_fingerprint", - "columns": [ - { - "expression": "org_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "env", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "fingerprint", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"customers\".\"fingerprint\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "customers_org_id_fkey": { - "name": "customers_org_id_fkey", - "tableFrom": "customers", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "cus_id_constraint": { - "name": "cus_id_constraint", - "nullsNotDistinct": false, - "columns": [ - "org_id", - "id", - "env" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.entities": { - "name": "entities", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "internal_id": { - "name": "internal_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "deleted": { - "name": "deleted", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "internal_feature_id": { - "name": "internal_feature_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "spend_limits": { - "name": "spend_limits", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "feature_id": { - "name": "feature_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_entities_internal_customer_id": { - "name": "idx_entities_internal_customer_id", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "hash", - "with": {} - }, - "idx_entities_customer_internal_desc": { - "name": "idx_entities_customer_internal_desc", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "\"internal_id\" DESC", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "entities_internal_customer_id_fkey": { - "name": "entities_internal_customer_id_fkey", - "tableFrom": "entities", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "entities_internal_feature_id_fkey": { - "name": "entities_internal_feature_id_fkey", - "tableFrom": "entities", - "tableTo": "features", - "columnsFrom": [ - "internal_feature_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "entities_org_id_fkey": { - "name": "entities_org_id_fkey", - "tableFrom": "entities", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "entity_id_constraint": { - "name": "entity_id_constraint", - "nullsNotDistinct": false, - "columns": [ - "org_id", - "env", - "internal_customer_id", - "id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.entitlements": { - "name": "entitlements", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "internal_feature_id": { - "name": "internal_feature_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_product_id": { - "name": "internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "is_custom": { - "name": "is_custom", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "allowance_type": { - "name": "allowance_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "allowance": { - "name": "allowance", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "interval": { - "name": "interval", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "interval_count": { - "name": "interval_count", - "type": "numeric", - "primaryKey": false, - "notNull": false, - "default": 1 - }, - "carry_from_previous": { - "name": "carry_from_previous", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "entity_feature_id": { - "name": "entity_feature_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "feature_id": { - "name": "feature_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "usage_limit": { - "name": "usage_limit", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "rollover": { - "name": "rollover", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_entitlements_internal_product_id": { - "name": "idx_entitlements_internal_product_id", - "columns": [ - { - "expression": "internal_product_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "entitlements_internal_feature_id_fkey": { - "name": "entitlements_internal_feature_id_fkey", - "tableFrom": "entitlements", - "tableTo": "features", - "columnsFrom": [ - "internal_feature_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "entitlements_internal_product_id_fkey": { - "name": "entitlements_internal_product_id_fkey", - "tableFrom": "entitlements", - "tableTo": "products", - "columnsFrom": [ - "internal_product_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "entitlements_id_key": { - "name": "entitlements_id_key", - "nullsNotDistinct": false, - "columns": [ - "id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.events": { - "name": "events", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "org_slug": { - "name": "org_slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "timestamp": { - "name": "timestamp", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "event_name": { - "name": "event_name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "value": { - "name": "value", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "set_usage": { - "name": "set_usage", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "entity_id": { - "name": "entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "internal_entity_id": { - "name": "internal_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "properties": { - "name": "properties", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_events_internal_customer_id": { - "name": "idx_events_internal_customer_id", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_events_internal_entity_id": { - "name": "idx_events_internal_entity_id", - "columns": [ - { - "expression": "internal_entity_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_events_customer_non_usage_ts": { - "name": "idx_events_customer_non_usage_ts", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "\"timestamp\" DESC", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "\"id\" DESC", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"events\".\"set_usage\" = false", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "events_internal_customer_id_fkey": { - "name": "events_internal_customer_id_fkey", - "tableFrom": "events", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "unique_event_constraint": { - "name": "unique_event_constraint", - "nullsNotDistinct": false, - "columns": [ - "org_id", - "env", - "customer_id", - "event_name", - "idempotency_key" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.features": { - "name": "features", - "schema": "", - "columns": { - "internal_id": { - "name": "internal_id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "display": { - "name": "display", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "archived": { - "name": "archived", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "event_names": { - "name": "event_names", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - } - }, - "indexes": {}, - "foreignKeys": { - "features_org_id_fkey": { - "name": "features_org_id_fkey", - "tableFrom": "features", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "feature_id_constraint": { - "name": "feature_id_constraint", - "nullsNotDistinct": false, - "columns": [ - "org_id", - "id", - "env" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.free_trials": { - "name": "free_trials", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "internal_product_id": { - "name": "internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "duration": { - "name": "duration", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'day'" - }, - "length": { - "name": "length", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "unique_fingerprint": { - "name": "unique_fingerprint", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "is_custom": { - "name": "is_custom", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "card_required": { - "name": "card_required", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "indexes": { - "idx_free_trials_internal_product_id": { - "name": "idx_free_trials_internal_product_id", - "columns": [ - { - "expression": "internal_product_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "free_trials_internal_product_id_fkey": { - "name": "free_trials_internal_product_id_fkey", - "tableFrom": "free_trials", - "tableTo": "products", - "columnsFrom": [ - "internal_product_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.invitation": { - "name": "invitation", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "inviter_id": { - "name": "inviter_id", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "invitation_organizationId_idx": { - "name": "invitation_organizationId_idx", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "invitation_email_idx": { - "name": "invitation_email_idx", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "invitation_organization_id_organizations_id_fk": { - "name": "invitation_organization_id_organizations_id_fk", - "tableFrom": "invitation", - "tableTo": "organizations", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "invitation_inviter_id_user_id_fk": { - "name": "invitation_inviter_id_user_id_fk", - "tableFrom": "invitation", - "tableTo": "user", - "columnsFrom": [ - "inviter_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.invoice_line_items": { - "name": "invoice_line_items", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "invoice_id": { - "name": "invoice_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_id": { - "name": "stripe_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_invoice_id": { - "name": "stripe_invoice_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_invoice_item_id": { - "name": "stripe_invoice_item_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_subscription_item_id": { - "name": "stripe_subscription_item_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_product_id": { - "name": "stripe_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_price_id": { - "name": "stripe_price_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_discountable": { - "name": "stripe_discountable", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "amount": { - "name": "amount", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "amount_after_discounts": { - "name": "amount_after_discounts", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "currency": { - "name": "currency", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'usd'" - }, - "stripe_quantity": { - "name": "stripe_quantity", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "total_quantity": { - "name": "total_quantity", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "paid_quantity": { - "name": "paid_quantity", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description_source": { - "name": "description_source", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "direction": { - "name": "direction", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "billing_timing": { - "name": "billing_timing", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "prorated": { - "name": "prorated", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "price_id": { - "name": "price_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "customer_product_ids": { - "name": "customer_product_ids", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "customer_price_ids": { - "name": "customer_price_ids", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "customer_entitlement_ids": { - "name": "customer_entitlement_ids", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "internal_product_id": { - "name": "internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "product_id": { - "name": "product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "internal_feature_id": { - "name": "internal_feature_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "feature_id": { - "name": "feature_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "effective_period_start": { - "name": "effective_period_start", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "effective_period_end": { - "name": "effective_period_end", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "discounts": { - "name": "discounts", - "type": "jsonb[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - } - }, - "indexes": {}, - "foreignKeys": { - "invoice_line_items_invoice_id_fkey": { - "name": "invoice_line_items_invoice_id_fkey", - "tableFrom": "invoice_line_items", - "tableTo": "invoices", - "columnsFrom": [ - "invoice_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "invoice_line_items_stripe_id_unique": { - "name": "invoice_line_items_stripe_id_unique", - "nullsNotDistinct": false, - "columns": [ - "stripe_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.invoices": { - "name": "invoices", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "product_ids": { - "name": "product_ids", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "internal_product_ids": { - "name": "internal_product_ids", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_entity_id": { - "name": "internal_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_id": { - "name": "stripe_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'draft'" - }, - "hosted_invoice_url": { - "name": "hosted_invoice_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "total": { - "name": "total", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "currency": { - "name": "currency", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'usd'" - }, - "discounts": { - "name": "discounts", - "type": "jsonb[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "items": { - "name": "items", - "type": "jsonb[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - } - }, - "indexes": { - "idx_invoices_customer_created": { - "name": "idx_invoices_customer_created", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "\"created_at\" DESC", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "\"id\" DESC", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "invoices_internal_customer_id_fkey": { - "name": "invoices_internal_customer_id_fkey", - "tableFrom": "invoices", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "invoices_internal_entity_id_fkey": { - "name": "invoices_internal_entity_id_fkey", - "tableFrom": "invoices", - "tableTo": "entities", - "columnsFrom": [ - "internal_entity_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "invoices_stripe_id_key": { - "name": "invoices_stripe_id_key", - "nullsNotDistinct": false, - "columns": [ - "stripe_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.jwks": { - "name": "jwks", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "public_key": { - "name": "public_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "private_key": { - "name": "private_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.member": { - "name": "member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'member'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "member_organizationId_idx": { - "name": "member_organizationId_idx", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "member_userId_idx": { - "name": "member_userId_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "member_organization_id_organizations_id_fk": { - "name": "member_organization_id_organizations_id_fk", - "tableFrom": "member", - "tableTo": "organizations", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "member_user_id_user_id_fk": { - "name": "member_user_id_user_id_fk", - "tableFrom": "member", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.metadata": { - "name": "metadata", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "expires_at": { - "name": "expires_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "data": { - "name": "data", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_invoice_id": { - "name": "stripe_invoice_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_checkout_session_id": { - "name": "stripe_checkout_session_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.migration_errors": { - "name": "migration_errors", - "schema": "", - "columns": { - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "migration_job_id": { - "name": "migration_job_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "data": { - "name": "data", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "message": { - "name": "message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "migration_customers_internal_customer_id_fkey": { - "name": "migration_customers_internal_customer_id_fkey", - "tableFrom": "migration_errors", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "migration_customers_migration_job_id_fkey": { - "name": "migration_customers_migration_job_id_fkey", - "tableFrom": "migration_errors", - "tableTo": "migration_jobs", - "columnsFrom": [ - "migration_job_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "migration_errors_pkey": { - "name": "migration_errors_pkey", - "columns": [ - "internal_customer_id", - "migration_job_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.migration_jobs": { - "name": "migration_jobs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "current_step": { - "name": "current_step", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "from_internal_product_id": { - "name": "from_internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "to_internal_product_id": { - "name": "to_internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "step_details": { - "name": "step_details", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "migration_jobs_from_internal_product_id_fkey": { - "name": "migration_jobs_from_internal_product_id_fkey", - "tableFrom": "migration_jobs", - "tableTo": "products", - "columnsFrom": [ - "from_internal_product_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "migration_jobs_org_id_fkey": { - "name": "migration_jobs_org_id_fkey", - "tableFrom": "migration_jobs", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "migration_jobs_to_internal_product_id_fkey": { - "name": "migration_jobs_to_internal_product_id_fkey", - "tableFrom": "migration_jobs", - "tableTo": "products", - "columnsFrom": [ - "to_internal_product_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.oauth_access_token": { - "name": "oauth_access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "session_id": { - "name": "session_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "reference_id": { - "name": "reference_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "refresh_id": { - "name": "refresh_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "scopes": { - "name": "scopes", - "type": "text[]", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "oauth_access_token_client_id_oauth_client_client_id_fk": { - "name": "oauth_access_token_client_id_oauth_client_client_id_fk", - "tableFrom": "oauth_access_token", - "tableTo": "oauth_client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "client_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "oauth_access_token_session_id_session_id_fk": { - "name": "oauth_access_token_session_id_session_id_fk", - "tableFrom": "oauth_access_token", - "tableTo": "session", - "columnsFrom": [ - "session_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "oauth_access_token_user_id_user_id_fk": { - "name": "oauth_access_token_user_id_user_id_fk", - "tableFrom": "oauth_access_token", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { - "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", - "tableFrom": "oauth_access_token", - "tableTo": "oauth_refresh_token", - "columnsFrom": [ - "refresh_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "oauth_access_token_token_unique": { - "name": "oauth_access_token_token_unique", - "nullsNotDistinct": false, - "columns": [ - "token" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.oauth_client": { - "name": "oauth_client", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_secret": { - "name": "client_secret", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "disabled": { - "name": "disabled", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "skip_consent": { - "name": "skip_consent", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "enable_end_session": { - "name": "enable_end_session", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "scopes": { - "name": "scopes", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "uri": { - "name": "uri", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "contacts": { - "name": "contacts", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "tos": { - "name": "tos", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "policy": { - "name": "policy", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "software_id": { - "name": "software_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "software_version": { - "name": "software_version", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "software_statement": { - "name": "software_statement", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "redirect_uris": { - "name": "redirect_uris", - "type": "text[]", - "primaryKey": false, - "notNull": true - }, - "post_logout_redirect_uris": { - "name": "post_logout_redirect_uris", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "token_endpoint_auth_method": { - "name": "token_endpoint_auth_method", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "grant_types": { - "name": "grant_types", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "response_types": { - "name": "response_types", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "public": { - "name": "public", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "reference_id": { - "name": "reference_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "oauth_client_user_id_user_id_fk": { - "name": "oauth_client_user_id_user_id_fk", - "tableFrom": "oauth_client", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "oauth_client_client_id_unique": { - "name": "oauth_client_client_id_unique", - "nullsNotDistinct": false, - "columns": [ - "client_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.oauth_consent": { - "name": "oauth_consent", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "reference_id": { - "name": "reference_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scopes": { - "name": "scopes", - "type": "text[]", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "oauth_consent_client_id_oauth_client_client_id_fk": { - "name": "oauth_consent_client_id_oauth_client_client_id_fk", - "tableFrom": "oauth_consent", - "tableTo": "oauth_client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "client_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "oauth_consent_user_id_user_id_fk": { - "name": "oauth_consent_user_id_user_id_fk", - "tableFrom": "oauth_consent", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.oauth_refresh_token": { - "name": "oauth_refresh_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "session_id": { - "name": "session_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "reference_id": { - "name": "reference_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "revoked": { - "name": "revoked", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "scopes": { - "name": "scopes", - "type": "text[]", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "oauth_refresh_token_client_id_oauth_client_client_id_fk": { - "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", - "tableFrom": "oauth_refresh_token", - "tableTo": "oauth_client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "client_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "oauth_refresh_token_session_id_session_id_fk": { - "name": "oauth_refresh_token_session_id_session_id_fk", - "tableFrom": "oauth_refresh_token", - "tableTo": "session", - "columnsFrom": [ - "session_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "oauth_refresh_token_user_id_user_id_fk": { - "name": "oauth_refresh_token_user_id_user_id_fk", - "tableFrom": "oauth_refresh_token", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.organizations": { - "name": "organizations", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "logo": { - "name": "logo", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "metadata": { - "name": "metadata", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "default_currency": { - "name": "default_currency", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'usd'" - }, - "stripe_connected": { - "name": "stripe_connected", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "stripe_config": { - "name": "stripe_config", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "test_stripe_connect": { - "name": "test_stripe_connect", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'::jsonb" - }, - "live_stripe_connect": { - "name": "live_stripe_connect", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'::jsonb" - }, - "processor_configs": { - "name": "processor_configs", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "test_pkey": { - "name": "test_pkey", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "live_pkey": { - "name": "live_pkey", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "svix_config": { - "name": "svix_config", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'::jsonb" - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "onboarded": { - "name": "onboarded", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "deployed": { - "name": "deployed", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "organizations_slug_unique": { - "name": "organizations_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - }, - "organizations_test_pkey_key": { - "name": "organizations_test_pkey_key", - "nullsNotDistinct": false, - "columns": [ - "test_pkey" - ] - }, - "organizations_live_pkey_key": { - "name": "organizations_live_pkey_key", - "nullsNotDistinct": false, - "columns": [ - "live_pkey" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.prices": { - "name": "prices", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_product_id": { - "name": "internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "billing_type": { - "name": "billing_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tier_behavior": { - "name": "tier_behavior", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "is_custom": { - "name": "is_custom", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "entitlement_id": { - "name": "entitlement_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "proration_config": { - "name": "proration_config", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "null" - } - }, - "indexes": { - "idx_prices_internal_product_id": { - "name": "idx_prices_internal_product_id", - "columns": [ - { - "expression": "internal_product_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_prices_entitlement_id": { - "name": "idx_prices_entitlement_id", - "columns": [ - { - "expression": "entitlement_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "prices_entitlement_id_fkey": { - "name": "prices_entitlement_id_fkey", - "tableFrom": "prices", - "tableTo": "entitlements", - "columnsFrom": [ - "entitlement_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "prices_internal_product_id_fkey": { - "name": "prices_internal_product_id_fkey", - "tableFrom": "prices", - "tableTo": "products", - "columnsFrom": [ - "internal_product_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "prices_id_key": { - "name": "prices_id_key", - "nullsNotDistinct": false, - "columns": [ - "id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.products": { - "name": "products", - "schema": "", - "columns": { - "internal_id": { - "name": "internal_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_add_on": { - "name": "is_add_on", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "is_default": { - "name": "is_default", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "group": { - "name": "group", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "''" - }, - "version": { - "name": "version", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 1 - }, - "processor": { - "name": "processor", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "base_variant_id": { - "name": "base_variant_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived": { - "name": "archived", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": {}, - "foreignKeys": { - "products_org_id_fkey": { - "name": "products_org_id_fkey", - "tableFrom": "products", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "unique_product": { - "name": "unique_product", - "nullsNotDistinct": false, - "columns": [ - "org_id", - "id", - "env", - "version" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.referral_codes": { - "name": "referral_codes", - "schema": "", - "columns": { - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "internal_reward_program_id": { - "name": "internal_reward_program_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "referral_codes_internal_customer_id_fkey": { - "name": "referral_codes_internal_customer_id_fkey", - "tableFrom": "referral_codes", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "referral_codes_internal_reward_program_id_fkey": { - "name": "referral_codes_internal_reward_program_id_fkey", - "tableFrom": "referral_codes", - "tableTo": "reward_programs", - "columnsFrom": [ - "internal_reward_program_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "referral_codes_org_id_fkey": { - "name": "referral_codes_org_id_fkey", - "tableFrom": "referral_codes", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "referral_codes_pkey": { - "name": "referral_codes_pkey", - "columns": [ - "code", - "org_id", - "env" - ] - } - }, - "uniqueConstraints": { - "referral_codes_id_key": { - "name": "referral_codes_id_key", - "nullsNotDistinct": false, - "columns": [ - "id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.replaceables": { - "name": "replaceables", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "cus_ent_id": { - "name": "cus_ent_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "from_entity_id": { - "name": "from_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "delete_next_cycle": { - "name": "delete_next_cycle", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "idx_replaceables_cus_ent_id": { - "name": "idx_replaceables_cus_ent_id", - "columns": [ - { - "expression": "cus_ent_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "replaceables_cus_ent_id_fkey": { - "name": "replaceables_cus_ent_id_fkey", - "tableFrom": "replaceables", - "tableTo": "customer_entitlements", - "columnsFrom": [ - "cus_ent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.revenuecat_mappings": { - "name": "revenuecat_mappings", - "schema": "", - "columns": { - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "autumn_product_id": { - "name": "autumn_product_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "revenuecat_product_ids": { - "name": "revenuecat_product_ids", - "type": "text[]", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - } - }, - "indexes": {}, - "foreignKeys": { - "revenuecat_mappings_org_id_fkey": { - "name": "revenuecat_mappings_org_id_fkey", - "tableFrom": "revenuecat_mappings", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "revenuecat_mappings_pkey": { - "name": "revenuecat_mappings_pkey", - "columns": [ - "org_id", - "env", - "autumn_product_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.reward_programs": { - "name": "reward_programs", - "schema": "", - "columns": { - "internal_id": { - "name": "internal_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "internal_reward_id": { - "name": "internal_reward_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "max_redemptions": { - "name": "max_redemptions", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "unlimited_redemptions": { - "name": "unlimited_redemptions", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "when": { - "name": "when", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'immediately'" - }, - "product_ids": { - "name": "product_ids", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{\"\"}'" - }, - "exclude_trial": { - "name": "exclude_trial", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "received_by": { - "name": "received_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "reward_triggers_internal_reward_id_fkey": { - "name": "reward_triggers_internal_reward_id_fkey", - "tableFrom": "reward_programs", - "tableTo": "rewards", - "columnsFrom": [ - "internal_reward_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "reward_triggers_org_id_fkey": { - "name": "reward_triggers_org_id_fkey", - "tableFrom": "reward_programs", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.reward_redemptions": { - "name": "reward_redemptions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "triggered": { - "name": "triggered", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "internal_reward_program_id": { - "name": "internal_reward_program_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "applied": { - "name": "applied", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "redeemer_applied": { - "name": "redeemer_applied", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "referral_code_id": { - "name": "referral_code_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "reward_redemptions_internal_customer_id_fkey": { - "name": "reward_redemptions_internal_customer_id_fkey", - "tableFrom": "reward_redemptions", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "reward_redemptions_internal_reward_program_id_fkey": { - "name": "reward_redemptions_internal_reward_program_id_fkey", - "tableFrom": "reward_redemptions", - "tableTo": "reward_programs", - "columnsFrom": [ - "internal_reward_program_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "reward_redemptions_referral_code_id_fkey": { - "name": "reward_redemptions_referral_code_id_fkey", - "tableFrom": "reward_redemptions", - "tableTo": "referral_codes", - "columnsFrom": [ - "referral_code_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.rewards": { - "name": "rewards", - "schema": "", - "columns": { - "internal_id": { - "name": "internal_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "discount_config": { - "name": "discount_config", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "free_product_config": { - "name": "free_product_config", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "free_product_id": { - "name": "free_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "promo_codes": { - "name": "promo_codes", - "type": "jsonb[]", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "coupons_org_id_fkey": { - "name": "coupons_org_id_fkey", - "tableFrom": "rewards", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.rollovers": { - "name": "rollovers", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "cus_ent_id": { - "name": "cus_ent_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "balance": { - "name": "balance", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "usage": { - "name": "usage", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "entities": { - "name": "entities", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - } - }, - "indexes": { - "idx_rollovers_cus_ent_id": { - "name": "idx_rollovers_cus_ent_id", - "columns": [ - { - "expression": "cus_ent_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_rollovers_cus_ent_expires": { - "name": "idx_rollovers_cus_ent_expires", - "columns": [ - { - "expression": "cus_ent_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "expires_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "rollover_cus_ent_id_fkey": { - "name": "rollover_cus_ent_id_fkey", - "tableFrom": "rollovers", - "tableTo": "customer_entitlements", - "columnsFrom": [ - "cus_ent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_agent": { - "name": "user_agent", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "impersonated_by": { - "name": "impersonated_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "active_organization_id": { - "name": "active_organization_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "city": { - "name": "city", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "country": { - "name": "country", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "session_userId_idx": { - "name": "session_userId_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "session_token_unique": { - "name": "session_token_unique", - "nullsNotDistinct": false, - "columns": [ - "token" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.subscriptions": { - "name": "subscriptions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "stripe_id": { - "name": "stripe_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_schedule_id": { - "name": "stripe_schedule_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'::jsonb" - }, - "usage_features": { - "name": "usage_features", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "current_period_start": { - "name": "current_period_start", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "current_period_end": { - "name": "current_period_end", - "type": "numeric", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "subscriptions_org_id_fkey": { - "name": "subscriptions_org_id_fkey", - "tableFrom": "subscriptions", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "subscriptions_stripe_id_key": { - "name": "subscriptions_stripe_id_key", - "nullsNotDistinct": false, - "columns": [ - "stripe_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "banned": { - "name": "banned", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "ban_reason": { - "name": "ban_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "ban_expires": { - "name": "ban_expires", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_active_at": { - "name": "last_active_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "user_created_by_fkey": { - "name": "user_created_by_fkey", - "tableFrom": "user", - "tableTo": "organizations", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_unique": { - "name": "user_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.vercel_resources": { - "name": "vercel_resources", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "installation_id": { - "name": "installation_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - } - }, - "indexes": {}, - "foreignKeys": { - "vercel_resources_org_id_fkey": { - "name": "vercel_resources_org_id_fkey", - "tableFrom": "vercel_resources", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "verification_identifier_idx": { - "name": "verification_identifier_idx", - "columns": [ - { - "expression": "identifier", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/shared/drizzle/meta/0001_snapshot.json b/shared/drizzle/meta/0001_snapshot.json deleted file mode 100644 index 12c4fe29c..000000000 --- a/shared/drizzle/meta/0001_snapshot.json +++ /dev/null @@ -1,5276 +0,0 @@ -{ - "id": "80d9d3d7-381f-4594-826d-ecb87b92a096", - "prevId": "3798c672-9c46-4dd0-9465-fc1b3f6bdc1e", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "account_id": { - "name": "account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token_expires_at": { - "name": "access_token_expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "refresh_token_expires_at": { - "name": "refresh_token_expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "account_userId_idx": { - "name": "account_userId_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "account_user_id_user_id_fk": { - "name": "account_user_id_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.actions": { - "name": "actions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "request_id": { - "name": "request_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "org_slug": { - "name": "org_slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "entity_id": { - "name": "entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "internal_entity_id": { - "name": "internal_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "auth_type": { - "name": "auth_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "method": { - "name": "method", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "timestamp": { - "name": "timestamp", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "properties": { - "name": "properties", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_actions_on_internal_entity_id": { - "name": "idx_actions_on_internal_entity_id", - "columns": [ - { - "expression": "internal_entity_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "actions_org_id_fkey": { - "name": "actions_org_id_fkey", - "tableFrom": "actions", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "actions_customer_id_fkey": { - "name": "actions_customer_id_fkey", - "tableFrom": "actions", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "actions_entity_id_fkey": { - "name": "actions_entity_id_fkey", - "tableFrom": "actions", - "tableTo": "entities", - "columnsFrom": [ - "internal_entity_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.api_keys": { - "name": "api_keys", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "prefix": { - "name": "prefix", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "hashed_key": { - "name": "hashed_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "meta": { - "name": "meta", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "api_keys_org_id_fkey": { - "name": "api_keys_org_id_fkey", - "tableFrom": "api_keys", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_keys_hashed_key_key": { - "name": "api_keys_hashed_key_key", - "nullsNotDistinct": false, - "columns": [ - "hashed_key" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.auto_topup_limit_states": { - "name": "auto_topup_limit_states", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "feature_id": { - "name": "feature_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "purchase_window_ends_at": { - "name": "purchase_window_ends_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "purchase_count": { - "name": "purchase_count", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "attempt_window_ends_at": { - "name": "attempt_window_ends_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "attempt_count": { - "name": "attempt_count", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "failed_attempt_window_ends_at": { - "name": "failed_attempt_window_ends_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "failed_attempt_count": { - "name": "failed_attempt_count", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "last_attempt_at": { - "name": "last_attempt_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "last_failed_attempt_at": { - "name": "last_failed_attempt_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "updated_at": { - "name": "updated_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - } - }, - "indexes": { - "auto_topup_limits_org_env_internal_customer_feature_unique": { - "name": "auto_topup_limits_org_env_internal_customer_feature_unique", - "columns": [ - { - "expression": "org_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "env", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "feature_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "auto_topup_limits_org_id_fkey": { - "name": "auto_topup_limits_org_id_fkey", - "tableFrom": "auto_topup_limit_states", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "auto_topup_limits_internal_customer_id_fkey": { - "name": "auto_topup_limits_internal_customer_id_fkey", - "tableFrom": "auto_topup_limit_states", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.chat_results": { - "name": "chat_results", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "data": { - "name": "data", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.checkouts": { - "name": "checkouts", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "params": { - "name": "params", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "params_version": { - "name": "params_version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "response": { - "name": "response", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "stripe_invoice_id": { - "name": "stripe_invoice_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "expires_at": { - "name": "expires_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "completed_at": { - "name": "completed_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_checkouts_stripe_invoice_id": { - "name": "idx_checkouts_stripe_invoice_id", - "columns": [ - { - "expression": "stripe_invoice_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.customer_entitlements": { - "name": "customer_entitlements", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "customer_product_id": { - "name": "customer_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "entitlement_id": { - "name": "entitlement_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text COLLATE \"C\"", - "primaryKey": false, - "notNull": true - }, - "internal_entity_id": { - "name": "internal_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "internal_feature_id": { - "name": "internal_feature_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "unlimited": { - "name": "unlimited", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "balance": { - "name": "balance", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "next_reset_at": { - "name": "next_reset_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "usage_allowed": { - "name": "usage_allowed", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "adjustment": { - "name": "adjustment", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "additional_balance": { - "name": "additional_balance", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "entities": { - "name": "entities", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "cache_version": { - "name": "cache_version", - "type": "integer", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "feature_id": { - "name": "feature_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "external_id": { - "name": "external_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_customer_entitlements_product_id": { - "name": "idx_customer_entitlements_product_id", - "columns": [ - { - "expression": "customer_product_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_customer_entitlements_internal_customer_id": { - "name": "idx_customer_entitlements_internal_customer_id", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_customer_entitlements_entitlement_id": { - "name": "idx_customer_entitlements_entitlement_id", - "columns": [ - { - "expression": "entitlement_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_customer_entitlements_loose_customer_expires": { - "name": "idx_customer_entitlements_loose_customer_expires", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "expires_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"customer_entitlements\".\"customer_product_id\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "entitlements_internal_feature_id_fkey": { - "name": "entitlements_internal_feature_id_fkey", - "tableFrom": "customer_entitlements", - "tableTo": "features", - "columnsFrom": [ - "internal_feature_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "customer_entitlements_internal_entity_id_fkey": { - "name": "customer_entitlements_internal_entity_id_fkey", - "tableFrom": "customer_entitlements", - "tableTo": "entities", - "columnsFrom": [ - "internal_entity_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "customer_entitlements_customer_product_id_fkey": { - "name": "customer_entitlements_customer_product_id_fkey", - "tableFrom": "customer_entitlements", - "tableTo": "customer_products", - "columnsFrom": [ - "customer_product_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "customer_entitlements_entitlement_id_fkey": { - "name": "customer_entitlements_entitlement_id_fkey", - "tableFrom": "customer_entitlements", - "tableTo": "entitlements", - "columnsFrom": [ - "entitlement_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.customer_prices": { - "name": "customer_prices", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "price_id": { - "name": "price_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "options": { - "name": "options", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "customer_product_id": { - "name": "customer_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_customer_prices_product_id": { - "name": "idx_customer_prices_product_id", - "columns": [ - { - "expression": "customer_product_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_customer_prices_price_id": { - "name": "idx_customer_prices_price_id", - "columns": [ - { - "expression": "price_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "customer_prices_customer_product_id_fkey": { - "name": "customer_prices_customer_product_id_fkey", - "tableFrom": "customer_prices", - "tableTo": "customer_products", - "columnsFrom": [ - "customer_product_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "customer_prices_internal_customer_id_fkey": { - "name": "customer_prices_internal_customer_id_fkey", - "tableFrom": "customer_prices", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "customer_prices_price_id_fkey": { - "name": "customer_prices_price_id_fkey", - "tableFrom": "customer_prices", - "tableTo": "prices", - "columnsFrom": [ - "price_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.customer_products": { - "name": "customer_products", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_product_id": { - "name": "internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_entity_id": { - "name": "internal_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "processor": { - "name": "processor", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "canceled": { - "name": "canceled", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "canceled_at": { - "name": "canceled_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "ended_at": { - "name": "ended_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "starts_at": { - "name": "starts_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "options": { - "name": "options", - "type": "jsonb[]", - "primaryKey": false, - "notNull": false - }, - "product_id": { - "name": "product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "free_trial_id": { - "name": "free_trial_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "trial_ends_at": { - "name": "trial_ends_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "collection_method": { - "name": "collection_method", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'charge_automatically'" - }, - "subscription_ids": { - "name": "subscription_ids", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "scheduled_ids": { - "name": "scheduled_ids", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "quantity": { - "name": "quantity", - "type": "numeric", - "primaryKey": false, - "notNull": false, - "default": 1 - }, - "is_custom": { - "name": "is_custom", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "entity_id": { - "name": "entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "billing_version": { - "name": "billing_version", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "api_version": { - "name": "api_version", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "api_semver": { - "name": "api_semver", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "external_id": { - "name": "external_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_customer_products_customer_status": { - "name": "idx_customer_products_customer_status", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_customer_products_on_internal_entity_id": { - "name": "idx_customer_products_on_internal_entity_id", - "columns": [ - { - "expression": "internal_entity_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "customer_products_free_trial_id_fkey": { - "name": "customer_products_free_trial_id_fkey", - "tableFrom": "customer_products", - "tableTo": "free_trials", - "columnsFrom": [ - "free_trial_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "customer_products_internal_customer_id_fkey": { - "name": "customer_products_internal_customer_id_fkey", - "tableFrom": "customer_products", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "customer_products_internal_product_id_fkey": { - "name": "customer_products_internal_product_id_fkey", - "tableFrom": "customer_products", - "tableTo": "products", - "columnsFrom": [ - "internal_product_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "customer_products_internal_entity_id_fkey": { - "name": "customer_products_internal_entity_id_fkey", - "tableFrom": "customer_products", - "tableTo": "entities", - "columnsFrom": [ - "internal_entity_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.customers": { - "name": "customers", - "schema": "", - "columns": { - "internal_id": { - "name": "internal_id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "fingerprint": { - "name": "fingerprint", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "processor": { - "name": "processor", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "processors": { - "name": "processors", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'::jsonb" - }, - "send_email_receipts": { - "name": "send_email_receipts", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "auto_topups": { - "name": "auto_topups", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "spend_limits": { - "name": "spend_limits", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "usage_alerts": { - "name": "usage_alerts", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "customers_email_null_id_unique": { - "name": "customers_email_null_id_unique", - "columns": [ - { - "expression": "org_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "env", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "lower(\"email\")", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"customers\".\"id\" IS NULL AND \"customers\".\"email\" IS NOT NULL AND \"customers\".\"email\" != ''", - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_customers_org_env_fingerprint": { - "name": "idx_customers_org_env_fingerprint", - "columns": [ - { - "expression": "org_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "env", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "fingerprint", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"customers\".\"fingerprint\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "customers_org_id_fkey": { - "name": "customers_org_id_fkey", - "tableFrom": "customers", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "cus_id_constraint": { - "name": "cus_id_constraint", - "nullsNotDistinct": false, - "columns": [ - "org_id", - "id", - "env" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.entities": { - "name": "entities", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "internal_id": { - "name": "internal_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "deleted": { - "name": "deleted", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "internal_feature_id": { - "name": "internal_feature_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "spend_limits": { - "name": "spend_limits", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "usage_alerts": { - "name": "usage_alerts", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "feature_id": { - "name": "feature_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_entities_internal_customer_id": { - "name": "idx_entities_internal_customer_id", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "hash", - "with": {} - }, - "idx_entities_customer_internal_desc": { - "name": "idx_entities_customer_internal_desc", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "\"internal_id\" DESC", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "entities_internal_customer_id_fkey": { - "name": "entities_internal_customer_id_fkey", - "tableFrom": "entities", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "entities_internal_feature_id_fkey": { - "name": "entities_internal_feature_id_fkey", - "tableFrom": "entities", - "tableTo": "features", - "columnsFrom": [ - "internal_feature_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "entities_org_id_fkey": { - "name": "entities_org_id_fkey", - "tableFrom": "entities", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "entity_id_constraint": { - "name": "entity_id_constraint", - "nullsNotDistinct": false, - "columns": [ - "org_id", - "env", - "internal_customer_id", - "id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.entitlements": { - "name": "entitlements", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "internal_feature_id": { - "name": "internal_feature_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_product_id": { - "name": "internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "is_custom": { - "name": "is_custom", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "allowance_type": { - "name": "allowance_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "allowance": { - "name": "allowance", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "interval": { - "name": "interval", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "interval_count": { - "name": "interval_count", - "type": "numeric", - "primaryKey": false, - "notNull": false, - "default": 1 - }, - "carry_from_previous": { - "name": "carry_from_previous", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "entity_feature_id": { - "name": "entity_feature_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "feature_id": { - "name": "feature_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "usage_limit": { - "name": "usage_limit", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "rollover": { - "name": "rollover", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_entitlements_internal_product_id": { - "name": "idx_entitlements_internal_product_id", - "columns": [ - { - "expression": "internal_product_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "entitlements_internal_feature_id_fkey": { - "name": "entitlements_internal_feature_id_fkey", - "tableFrom": "entitlements", - "tableTo": "features", - "columnsFrom": [ - "internal_feature_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "entitlements_internal_product_id_fkey": { - "name": "entitlements_internal_product_id_fkey", - "tableFrom": "entitlements", - "tableTo": "products", - "columnsFrom": [ - "internal_product_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "entitlements_id_key": { - "name": "entitlements_id_key", - "nullsNotDistinct": false, - "columns": [ - "id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.events": { - "name": "events", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "org_slug": { - "name": "org_slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "timestamp": { - "name": "timestamp", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "event_name": { - "name": "event_name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "idempotency_key": { - "name": "idempotency_key", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "value": { - "name": "value", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "set_usage": { - "name": "set_usage", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "entity_id": { - "name": "entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "internal_entity_id": { - "name": "internal_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "properties": { - "name": "properties", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_events_internal_customer_id": { - "name": "idx_events_internal_customer_id", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_events_internal_entity_id": { - "name": "idx_events_internal_entity_id", - "columns": [ - { - "expression": "internal_entity_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_events_customer_non_usage_ts": { - "name": "idx_events_customer_non_usage_ts", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "\"timestamp\" DESC", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "\"id\" DESC", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"events\".\"set_usage\" = false", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "events_internal_customer_id_fkey": { - "name": "events_internal_customer_id_fkey", - "tableFrom": "events", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "unique_event_constraint": { - "name": "unique_event_constraint", - "nullsNotDistinct": false, - "columns": [ - "org_id", - "env", - "customer_id", - "event_name", - "idempotency_key" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.features": { - "name": "features", - "schema": "", - "columns": { - "internal_id": { - "name": "internal_id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "display": { - "name": "display", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "archived": { - "name": "archived", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "event_names": { - "name": "event_names", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - } - }, - "indexes": {}, - "foreignKeys": { - "features_org_id_fkey": { - "name": "features_org_id_fkey", - "tableFrom": "features", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "feature_id_constraint": { - "name": "feature_id_constraint", - "nullsNotDistinct": false, - "columns": [ - "org_id", - "id", - "env" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.free_trials": { - "name": "free_trials", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "internal_product_id": { - "name": "internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "duration": { - "name": "duration", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'day'" - }, - "length": { - "name": "length", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "unique_fingerprint": { - "name": "unique_fingerprint", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "is_custom": { - "name": "is_custom", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "card_required": { - "name": "card_required", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "indexes": { - "idx_free_trials_internal_product_id": { - "name": "idx_free_trials_internal_product_id", - "columns": [ - { - "expression": "internal_product_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "free_trials_internal_product_id_fkey": { - "name": "free_trials_internal_product_id_fkey", - "tableFrom": "free_trials", - "tableTo": "products", - "columnsFrom": [ - "internal_product_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.invitation": { - "name": "invitation", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "inviter_id": { - "name": "inviter_id", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "invitation_organizationId_idx": { - "name": "invitation_organizationId_idx", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "invitation_email_idx": { - "name": "invitation_email_idx", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "invitation_organization_id_organizations_id_fk": { - "name": "invitation_organization_id_organizations_id_fk", - "tableFrom": "invitation", - "tableTo": "organizations", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "invitation_inviter_id_user_id_fk": { - "name": "invitation_inviter_id_user_id_fk", - "tableFrom": "invitation", - "tableTo": "user", - "columnsFrom": [ - "inviter_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.invoice_line_items": { - "name": "invoice_line_items", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "invoice_id": { - "name": "invoice_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_id": { - "name": "stripe_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_invoice_id": { - "name": "stripe_invoice_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_invoice_item_id": { - "name": "stripe_invoice_item_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_subscription_item_id": { - "name": "stripe_subscription_item_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_product_id": { - "name": "stripe_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_price_id": { - "name": "stripe_price_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_discountable": { - "name": "stripe_discountable", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "amount": { - "name": "amount", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "amount_after_discounts": { - "name": "amount_after_discounts", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "currency": { - "name": "currency", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'usd'" - }, - "stripe_quantity": { - "name": "stripe_quantity", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "total_quantity": { - "name": "total_quantity", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "paid_quantity": { - "name": "paid_quantity", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description_source": { - "name": "description_source", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "direction": { - "name": "direction", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "billing_timing": { - "name": "billing_timing", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "prorated": { - "name": "prorated", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "price_id": { - "name": "price_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "customer_product_ids": { - "name": "customer_product_ids", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "customer_price_ids": { - "name": "customer_price_ids", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "customer_entitlement_ids": { - "name": "customer_entitlement_ids", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "internal_product_id": { - "name": "internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "product_id": { - "name": "product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "internal_feature_id": { - "name": "internal_feature_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "feature_id": { - "name": "feature_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "effective_period_start": { - "name": "effective_period_start", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "effective_period_end": { - "name": "effective_period_end", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "discounts": { - "name": "discounts", - "type": "jsonb[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - } - }, - "indexes": {}, - "foreignKeys": { - "invoice_line_items_invoice_id_fkey": { - "name": "invoice_line_items_invoice_id_fkey", - "tableFrom": "invoice_line_items", - "tableTo": "invoices", - "columnsFrom": [ - "invoice_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "invoice_line_items_stripe_id_unique": { - "name": "invoice_line_items_stripe_id_unique", - "nullsNotDistinct": false, - "columns": [ - "stripe_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.invoices": { - "name": "invoices", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "product_ids": { - "name": "product_ids", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "internal_product_ids": { - "name": "internal_product_ids", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_entity_id": { - "name": "internal_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_id": { - "name": "stripe_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'draft'" - }, - "hosted_invoice_url": { - "name": "hosted_invoice_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "total": { - "name": "total", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "currency": { - "name": "currency", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'usd'" - }, - "discounts": { - "name": "discounts", - "type": "jsonb[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "items": { - "name": "items", - "type": "jsonb[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - } - }, - "indexes": { - "idx_invoices_customer_created": { - "name": "idx_invoices_customer_created", - "columns": [ - { - "expression": "internal_customer_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "\"created_at\" DESC", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "\"id\" DESC", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "invoices_internal_customer_id_fkey": { - "name": "invoices_internal_customer_id_fkey", - "tableFrom": "invoices", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "invoices_internal_entity_id_fkey": { - "name": "invoices_internal_entity_id_fkey", - "tableFrom": "invoices", - "tableTo": "entities", - "columnsFrom": [ - "internal_entity_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "invoices_stripe_id_key": { - "name": "invoices_stripe_id_key", - "nullsNotDistinct": false, - "columns": [ - "stripe_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.jwks": { - "name": "jwks", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "public_key": { - "name": "public_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "private_key": { - "name": "private_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.member": { - "name": "member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'member'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "member_organizationId_idx": { - "name": "member_organizationId_idx", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "member_userId_idx": { - "name": "member_userId_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "member_organization_id_organizations_id_fk": { - "name": "member_organization_id_organizations_id_fk", - "tableFrom": "member", - "tableTo": "organizations", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "member_user_id_user_id_fk": { - "name": "member_user_id_user_id_fk", - "tableFrom": "member", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.metadata": { - "name": "metadata", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "expires_at": { - "name": "expires_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "data": { - "name": "data", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_invoice_id": { - "name": "stripe_invoice_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_checkout_session_id": { - "name": "stripe_checkout_session_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.migration_errors": { - "name": "migration_errors", - "schema": "", - "columns": { - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "migration_job_id": { - "name": "migration_job_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "data": { - "name": "data", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "message": { - "name": "message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "migration_customers_internal_customer_id_fkey": { - "name": "migration_customers_internal_customer_id_fkey", - "tableFrom": "migration_errors", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "migration_customers_migration_job_id_fkey": { - "name": "migration_customers_migration_job_id_fkey", - "tableFrom": "migration_errors", - "tableTo": "migration_jobs", - "columnsFrom": [ - "migration_job_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "migration_errors_pkey": { - "name": "migration_errors_pkey", - "columns": [ - "internal_customer_id", - "migration_job_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.migration_jobs": { - "name": "migration_jobs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "current_step": { - "name": "current_step", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "from_internal_product_id": { - "name": "from_internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "to_internal_product_id": { - "name": "to_internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "step_details": { - "name": "step_details", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "migration_jobs_from_internal_product_id_fkey": { - "name": "migration_jobs_from_internal_product_id_fkey", - "tableFrom": "migration_jobs", - "tableTo": "products", - "columnsFrom": [ - "from_internal_product_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "migration_jobs_org_id_fkey": { - "name": "migration_jobs_org_id_fkey", - "tableFrom": "migration_jobs", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "migration_jobs_to_internal_product_id_fkey": { - "name": "migration_jobs_to_internal_product_id_fkey", - "tableFrom": "migration_jobs", - "tableTo": "products", - "columnsFrom": [ - "to_internal_product_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.oauth_access_token": { - "name": "oauth_access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "session_id": { - "name": "session_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "reference_id": { - "name": "reference_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "refresh_id": { - "name": "refresh_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "scopes": { - "name": "scopes", - "type": "text[]", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "oauth_access_token_client_id_oauth_client_client_id_fk": { - "name": "oauth_access_token_client_id_oauth_client_client_id_fk", - "tableFrom": "oauth_access_token", - "tableTo": "oauth_client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "client_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "oauth_access_token_session_id_session_id_fk": { - "name": "oauth_access_token_session_id_session_id_fk", - "tableFrom": "oauth_access_token", - "tableTo": "session", - "columnsFrom": [ - "session_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "oauth_access_token_user_id_user_id_fk": { - "name": "oauth_access_token_user_id_user_id_fk", - "tableFrom": "oauth_access_token", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { - "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", - "tableFrom": "oauth_access_token", - "tableTo": "oauth_refresh_token", - "columnsFrom": [ - "refresh_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "oauth_access_token_token_unique": { - "name": "oauth_access_token_token_unique", - "nullsNotDistinct": false, - "columns": [ - "token" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.oauth_client": { - "name": "oauth_client", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_secret": { - "name": "client_secret", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "disabled": { - "name": "disabled", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "skip_consent": { - "name": "skip_consent", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "enable_end_session": { - "name": "enable_end_session", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "scopes": { - "name": "scopes", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "uri": { - "name": "uri", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "icon": { - "name": "icon", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "contacts": { - "name": "contacts", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "tos": { - "name": "tos", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "policy": { - "name": "policy", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "software_id": { - "name": "software_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "software_version": { - "name": "software_version", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "software_statement": { - "name": "software_statement", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "redirect_uris": { - "name": "redirect_uris", - "type": "text[]", - "primaryKey": false, - "notNull": true - }, - "post_logout_redirect_uris": { - "name": "post_logout_redirect_uris", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "token_endpoint_auth_method": { - "name": "token_endpoint_auth_method", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "grant_types": { - "name": "grant_types", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "response_types": { - "name": "response_types", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "public": { - "name": "public", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "reference_id": { - "name": "reference_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "oauth_client_user_id_user_id_fk": { - "name": "oauth_client_user_id_user_id_fk", - "tableFrom": "oauth_client", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "oauth_client_client_id_unique": { - "name": "oauth_client_client_id_unique", - "nullsNotDistinct": false, - "columns": [ - "client_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.oauth_consent": { - "name": "oauth_consent", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "reference_id": { - "name": "reference_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "scopes": { - "name": "scopes", - "type": "text[]", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "oauth_consent_client_id_oauth_client_client_id_fk": { - "name": "oauth_consent_client_id_oauth_client_client_id_fk", - "tableFrom": "oauth_consent", - "tableTo": "oauth_client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "client_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "oauth_consent_user_id_user_id_fk": { - "name": "oauth_consent_user_id_user_id_fk", - "tableFrom": "oauth_consent", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.oauth_refresh_token": { - "name": "oauth_refresh_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_id": { - "name": "client_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "session_id": { - "name": "session_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "reference_id": { - "name": "reference_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "revoked": { - "name": "revoked", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "scopes": { - "name": "scopes", - "type": "text[]", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "oauth_refresh_token_client_id_oauth_client_client_id_fk": { - "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", - "tableFrom": "oauth_refresh_token", - "tableTo": "oauth_client", - "columnsFrom": [ - "client_id" - ], - "columnsTo": [ - "client_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "oauth_refresh_token_session_id_session_id_fk": { - "name": "oauth_refresh_token_session_id_session_id_fk", - "tableFrom": "oauth_refresh_token", - "tableTo": "session", - "columnsFrom": [ - "session_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "oauth_refresh_token_user_id_user_id_fk": { - "name": "oauth_refresh_token_user_id_user_id_fk", - "tableFrom": "oauth_refresh_token", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.organizations": { - "name": "organizations", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "logo": { - "name": "logo", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "metadata": { - "name": "metadata", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "default_currency": { - "name": "default_currency", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'usd'" - }, - "stripe_connected": { - "name": "stripe_connected", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "stripe_config": { - "name": "stripe_config", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "test_stripe_connect": { - "name": "test_stripe_connect", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'::jsonb" - }, - "live_stripe_connect": { - "name": "live_stripe_connect", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'::jsonb" - }, - "processor_configs": { - "name": "processor_configs", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "test_pkey": { - "name": "test_pkey", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "live_pkey": { - "name": "live_pkey", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "svix_config": { - "name": "svix_config", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'::jsonb" - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "onboarded": { - "name": "onboarded", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "deployed": { - "name": "deployed", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "organizations_slug_unique": { - "name": "organizations_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - }, - "organizations_test_pkey_key": { - "name": "organizations_test_pkey_key", - "nullsNotDistinct": false, - "columns": [ - "test_pkey" - ] - }, - "organizations_live_pkey_key": { - "name": "organizations_live_pkey_key", - "nullsNotDistinct": false, - "columns": [ - "live_pkey" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.prices": { - "name": "prices", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text COLLATE \"C\"", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_product_id": { - "name": "internal_product_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "billing_type": { - "name": "billing_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tier_behavior": { - "name": "tier_behavior", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "is_custom": { - "name": "is_custom", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "entitlement_id": { - "name": "entitlement_id", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "proration_config": { - "name": "proration_config", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "null" - } - }, - "indexes": { - "idx_prices_internal_product_id": { - "name": "idx_prices_internal_product_id", - "columns": [ - { - "expression": "internal_product_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_prices_entitlement_id": { - "name": "idx_prices_entitlement_id", - "columns": [ - { - "expression": "entitlement_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "prices_entitlement_id_fkey": { - "name": "prices_entitlement_id_fkey", - "tableFrom": "prices", - "tableTo": "entitlements", - "columnsFrom": [ - "entitlement_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "prices_internal_product_id_fkey": { - "name": "prices_internal_product_id_fkey", - "tableFrom": "prices", - "tableTo": "products", - "columnsFrom": [ - "internal_product_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "prices_id_key": { - "name": "prices_id_key", - "nullsNotDistinct": false, - "columns": [ - "id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.products": { - "name": "products", - "schema": "", - "columns": { - "internal_id": { - "name": "internal_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_add_on": { - "name": "is_add_on", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "is_default": { - "name": "is_default", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "group": { - "name": "group", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "''" - }, - "version": { - "name": "version", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 1 - }, - "processor": { - "name": "processor", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "null" - }, - "base_variant_id": { - "name": "base_variant_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived": { - "name": "archived", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": {}, - "foreignKeys": { - "products_org_id_fkey": { - "name": "products_org_id_fkey", - "tableFrom": "products", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "unique_product": { - "name": "unique_product", - "nullsNotDistinct": false, - "columns": [ - "org_id", - "id", - "env", - "version" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.referral_codes": { - "name": "referral_codes", - "schema": "", - "columns": { - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "internal_reward_program_id": { - "name": "internal_reward_program_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "referral_codes_internal_customer_id_fkey": { - "name": "referral_codes_internal_customer_id_fkey", - "tableFrom": "referral_codes", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "referral_codes_internal_reward_program_id_fkey": { - "name": "referral_codes_internal_reward_program_id_fkey", - "tableFrom": "referral_codes", - "tableTo": "reward_programs", - "columnsFrom": [ - "internal_reward_program_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "referral_codes_org_id_fkey": { - "name": "referral_codes_org_id_fkey", - "tableFrom": "referral_codes", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "referral_codes_pkey": { - "name": "referral_codes_pkey", - "columns": [ - "code", - "org_id", - "env" - ] - } - }, - "uniqueConstraints": { - "referral_codes_id_key": { - "name": "referral_codes_id_key", - "nullsNotDistinct": false, - "columns": [ - "id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.replaceables": { - "name": "replaceables", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "cus_ent_id": { - "name": "cus_ent_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "from_entity_id": { - "name": "from_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "delete_next_cycle": { - "name": "delete_next_cycle", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "idx_replaceables_cus_ent_id": { - "name": "idx_replaceables_cus_ent_id", - "columns": [ - { - "expression": "cus_ent_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "replaceables_cus_ent_id_fkey": { - "name": "replaceables_cus_ent_id_fkey", - "tableFrom": "replaceables", - "tableTo": "customer_entitlements", - "columnsFrom": [ - "cus_ent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.revenuecat_mappings": { - "name": "revenuecat_mappings", - "schema": "", - "columns": { - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "autumn_product_id": { - "name": "autumn_product_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "revenuecat_product_ids": { - "name": "revenuecat_product_ids", - "type": "text[]", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - } - }, - "indexes": {}, - "foreignKeys": { - "revenuecat_mappings_org_id_fkey": { - "name": "revenuecat_mappings_org_id_fkey", - "tableFrom": "revenuecat_mappings", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "revenuecat_mappings_pkey": { - "name": "revenuecat_mappings_pkey", - "columns": [ - "org_id", - "env", - "autumn_product_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.reward_programs": { - "name": "reward_programs", - "schema": "", - "columns": { - "internal_id": { - "name": "internal_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "internal_reward_id": { - "name": "internal_reward_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "max_redemptions": { - "name": "max_redemptions", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "unlimited_redemptions": { - "name": "unlimited_redemptions", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "when": { - "name": "when", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'immediately'" - }, - "product_ids": { - "name": "product_ids", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{\"\"}'" - }, - "exclude_trial": { - "name": "exclude_trial", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "received_by": { - "name": "received_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "reward_triggers_internal_reward_id_fkey": { - "name": "reward_triggers_internal_reward_id_fkey", - "tableFrom": "reward_programs", - "tableTo": "rewards", - "columnsFrom": [ - "internal_reward_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "reward_triggers_org_id_fkey": { - "name": "reward_triggers_org_id_fkey", - "tableFrom": "reward_programs", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.reward_redemptions": { - "name": "reward_redemptions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "internal_customer_id": { - "name": "internal_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "triggered": { - "name": "triggered", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "internal_reward_program_id": { - "name": "internal_reward_program_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "applied": { - "name": "applied", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "redeemer_applied": { - "name": "redeemer_applied", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "referral_code_id": { - "name": "referral_code_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "reward_redemptions_internal_customer_id_fkey": { - "name": "reward_redemptions_internal_customer_id_fkey", - "tableFrom": "reward_redemptions", - "tableTo": "customers", - "columnsFrom": [ - "internal_customer_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "reward_redemptions_internal_reward_program_id_fkey": { - "name": "reward_redemptions_internal_reward_program_id_fkey", - "tableFrom": "reward_redemptions", - "tableTo": "reward_programs", - "columnsFrom": [ - "internal_reward_program_id" - ], - "columnsTo": [ - "internal_id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "reward_redemptions_referral_code_id_fkey": { - "name": "reward_redemptions_referral_code_id_fkey", - "tableFrom": "reward_redemptions", - "tableTo": "referral_codes", - "columnsFrom": [ - "referral_code_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.rewards": { - "name": "rewards", - "schema": "", - "columns": { - "internal_id": { - "name": "internal_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "discount_config": { - "name": "discount_config", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "free_product_config": { - "name": "free_product_config", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "free_product_id": { - "name": "free_product_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "promo_codes": { - "name": "promo_codes", - "type": "jsonb[]", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "coupons_org_id_fkey": { - "name": "coupons_org_id_fkey", - "tableFrom": "rewards", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.rollovers": { - "name": "rollovers", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "cus_ent_id": { - "name": "cus_ent_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "balance": { - "name": "balance", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "usage": { - "name": "usage", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "entities": { - "name": "entities", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - } - }, - "indexes": { - "idx_rollovers_cus_ent_id": { - "name": "idx_rollovers_cus_ent_id", - "columns": [ - { - "expression": "cus_ent_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_rollovers_cus_ent_expires": { - "name": "idx_rollovers_cus_ent_expires", - "columns": [ - { - "expression": "cus_ent_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "expires_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "rollover_cus_ent_id_fkey": { - "name": "rollover_cus_ent_id_fkey", - "tableFrom": "rollovers", - "tableTo": "customer_entitlements", - "columnsFrom": [ - "cus_ent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_agent": { - "name": "user_agent", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "impersonated_by": { - "name": "impersonated_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "active_organization_id": { - "name": "active_organization_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "city": { - "name": "city", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "country": { - "name": "country", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "session_userId_idx": { - "name": "session_userId_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "session_token_unique": { - "name": "session_token_unique", - "nullsNotDistinct": false, - "columns": [ - "token" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - }, - "public.subscriptions": { - "name": "subscriptions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "stripe_id": { - "name": "stripe_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_schedule_id": { - "name": "stripe_schedule_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "ROUND(date_part('epoch', NOW()) * 1000)::BIGINT" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'::jsonb" - }, - "usage_features": { - "name": "usage_features", - "type": "text[]", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "current_period_start": { - "name": "current_period_start", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "current_period_end": { - "name": "current_period_end", - "type": "numeric", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "subscriptions_org_id_fkey": { - "name": "subscriptions_org_id_fkey", - "tableFrom": "subscriptions", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "subscriptions_stripe_id_key": { - "name": "subscriptions_stripe_id_key", - "nullsNotDistinct": false, - "columns": [ - "stripe_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "banned": { - "name": "banned", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "ban_reason": { - "name": "ban_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "ban_expires": { - "name": "ban_expires", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_active_at": { - "name": "last_active_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "user_created_by_fkey": { - "name": "user_created_by_fkey", - "tableFrom": "user", - "tableTo": "organizations", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_unique": { - "name": "user_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.vercel_resources": { - "name": "vercel_resources", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "org_id": { - "name": "org_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "env": { - "name": "env", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "installation_id": { - "name": "installation_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - } - }, - "indexes": {}, - "foreignKeys": { - "vercel_resources_org_id_fkey": { - "name": "vercel_resources_org_id_fkey", - "tableFrom": "vercel_resources", - "tableTo": "organizations", - "columnsFrom": [ - "org_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "verification_identifier_idx": { - "name": "verification_identifier_idx", - "columns": [ - { - "expression": "identifier", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": true - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/shared/drizzle/meta/_journal.json b/shared/drizzle/meta/_journal.json deleted file mode 100644 index a07fa4e42..000000000 --- a/shared/drizzle/meta/_journal.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1773773937035, - "tag": "0000_rainy_siren", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1773916904198, - "tag": "0001_fixed_whizzer", - "breakpoints": true - } - ] -} \ No newline at end of file From aba7df759841f18cf617b4b276c0884f5ab9729c Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 11:28:27 +0000 Subject: [PATCH 04/49] feat: add percentage-based usage alerts --- .../billingControls/entityBillingControls.ts | 4 -- .../billingControls/entityBillingControls.ts | 4 -- .../cusModels/billingControls/usageAlert.ts | 65 +++++++++++++------ .../cusModels/entityModels/entityModels.ts | 6 +- .../cusModels/entityModels/entityTable.ts | 6 +- 5 files changed, 47 insertions(+), 38 deletions(-) diff --git a/shared/api/billingControls/entityBillingControls.ts b/shared/api/billingControls/entityBillingControls.ts index 179d63638..693f5b15a 100644 --- a/shared/api/billingControls/entityBillingControls.ts +++ b/shared/api/billingControls/entityBillingControls.ts @@ -1,14 +1,10 @@ import { z } from "zod/v4"; import { ApiSpendLimitSchema } from "./spendLimit.js"; -import { ApiUsageAlertSchema } from "./usageAlert.js"; export const ApiEntityBillingControlsSchema = z.object({ spend_limits: z.array(ApiSpendLimitSchema).optional().meta({ description: "List of overage spend limits per feature.", }), - usage_alerts: z.array(ApiUsageAlertSchema).optional().meta({ - description: "List of usage alert configurations per feature.", - }), }); export const ApiEntityBillingControlsParamsSchema = diff --git a/shared/models/cusModels/billingControls/entityBillingControls.ts b/shared/models/cusModels/billingControls/entityBillingControls.ts index d815fcd2a..afde88943 100644 --- a/shared/models/cusModels/billingControls/entityBillingControls.ts +++ b/shared/models/cusModels/billingControls/entityBillingControls.ts @@ -1,14 +1,10 @@ import { z } from "zod/v4"; import { DbSpendLimitSchema } from "./spendLimit.js"; -import { DbUsageAlertSchema } from "./usageAlert.js"; export const EntityBillingControlsSchema = z.object({ spend_limits: z.array(DbSpendLimitSchema).optional().meta({ description: "List of overage spend limits per feature.", }), - usage_alerts: z.array(DbUsageAlertSchema).optional().meta({ - description: "List of usage alert configurations per feature.", - }), }); export type EntityBillingControls = z.infer; diff --git a/shared/models/cusModels/billingControls/usageAlert.ts b/shared/models/cusModels/billingControls/usageAlert.ts index 630041adc..113cbebbc 100644 --- a/shared/models/cusModels/billingControls/usageAlert.ts +++ b/shared/models/cusModels/billingControls/usageAlert.ts @@ -1,25 +1,50 @@ import { z } from "zod/v4"; -export const UsageAlertThresholdType = z.enum(["usage", "balance"]); +export const DbUsageAlertSchema = z + .object({ + feature_id: z.string().optional().meta({ + description: + "The feature ID this alert applies to. If omitted, the alert applies globally.", + }), + enabled: z.boolean().default(true).meta({ + description: "Whether this usage alert is enabled.", + }), + usage_threshold: z.number().min(0).optional().meta({ + description: + "Absolute usage count that triggers the alert.", + }), + usage_percentage_threshold: z.number().min(0).max(100).optional().meta({ + description: + "Percentage of the usage allowance (0-100) that triggers the alert.", + }), + name: z.string().optional().meta({ + description: + "Optional user-defined label to distinguish multiple alerts on the same feature.", + }), + }) + .check((ctx) => { + const { usage_threshold, usage_percentage_threshold } = ctx.value; + const hasUsage = usage_threshold !== undefined; + const hasPercentage = usage_percentage_threshold !== undefined; -export const DbUsageAlertSchema = z.object({ - feature_id: z.string().optional().meta({ - description: "The feature id this alert applies to. If not included, the alert applies globally.", - }), - enabled: z.boolean().default(true).meta({ - description: "Whether this usage alert is enabled.", - }), - threshold: z.number().min(0).meta({ - description: "The threshold value that triggers the alert.", - }), - threshold_type: UsageAlertThresholdType.meta({ - description: - 'Whether the threshold is based on, can be usage or balance.', - }), - name: z.string().optional().meta({ - description: - "Optional user-defined label to name an alerts.", - }), -}); + if (!hasUsage && !hasPercentage) { + ctx.issues.push({ + code: "custom", + input: ctx.value, + message: + "At least one of usage_threshold or usage_percentage_threshold must be provided", + }); + return; + } + + if (hasUsage && hasPercentage) { + ctx.issues.push({ + code: "custom", + input: ctx.value, + message: + "Only one of usage_threshold or usage_percentage_threshold can be provided, not both", + }); + } + }); export type DbUsageAlert = z.infer; diff --git a/shared/models/cusModels/entityModels/entityModels.ts b/shared/models/cusModels/entityModels/entityModels.ts index 25c8413f0..f63ffbace 100644 --- a/shared/models/cusModels/entityModels/entityModels.ts +++ b/shared/models/cusModels/entityModels/entityModels.ts @@ -1,9 +1,6 @@ import { z } from "zod/v4"; import type { Feature } from "../../featureModels/featureModels.js"; -import { - DbSpendLimitSchema, - DbUsageAlertSchema, -} from "../billingControls/customerBillingControls.js"; +import { DbSpendLimitSchema } from "../billingControls/customerBillingControls.js"; export const EntitySchema = z.object({ id: z.string().nullable(), @@ -17,7 +14,6 @@ export const EntitySchema = z.object({ feature_id: z.string(), internal_feature_id: z.string(), spend_limits: z.array(DbSpendLimitSchema).nullish(), - usage_alerts: z.array(DbUsageAlertSchema).nullish(), }); // export const CreateEntitySchema = z.object({ diff --git a/shared/models/cusModels/entityModels/entityTable.ts b/shared/models/cusModels/entityModels/entityTable.ts index 5cfe847b6..9715bef54 100644 --- a/shared/models/cusModels/entityModels/entityTable.ts +++ b/shared/models/cusModels/entityModels/entityTable.ts @@ -11,10 +11,7 @@ import { } from "drizzle-orm/pg-core"; import { features } from "../../featureModels/featureTable.js"; import { organizations } from "../../orgModels/orgTable.js"; -import type { - DbSpendLimit, - DbUsageAlert, -} from "../billingControls/customerBillingControls.js"; +import type { DbSpendLimit } from "../billingControls/customerBillingControls.js"; import { customers } from "../cusTable.js"; export const entities = pgTable( @@ -30,7 +27,6 @@ export const entities = pgTable( deleted: boolean().default(false).notNull(), internal_feature_id: text("internal_feature_id"), spend_limits: jsonb().$type(), - usage_alerts: jsonb().$type(), // Optional... feature_id: text("feature_id"), From 4ca1bf71e65383336fb2403f63e5b721703b5615 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 11:43:09 +0000 Subject: [PATCH 05/49] switch to threshold_type and enum validation --- .../cusModels/billingControls/usageAlert.ts | 70 +++++++------------ 1 file changed, 25 insertions(+), 45 deletions(-) diff --git a/shared/models/cusModels/billingControls/usageAlert.ts b/shared/models/cusModels/billingControls/usageAlert.ts index 113cbebbc..8424cc787 100644 --- a/shared/models/cusModels/billingControls/usageAlert.ts +++ b/shared/models/cusModels/billingControls/usageAlert.ts @@ -1,50 +1,30 @@ import { z } from "zod/v4"; -export const DbUsageAlertSchema = z - .object({ - feature_id: z.string().optional().meta({ - description: - "The feature ID this alert applies to. If omitted, the alert applies globally.", - }), - enabled: z.boolean().default(true).meta({ - description: "Whether this usage alert is enabled.", - }), - usage_threshold: z.number().min(0).optional().meta({ - description: - "Absolute usage count that triggers the alert.", - }), - usage_percentage_threshold: z.number().min(0).max(100).optional().meta({ - description: - "Percentage of the usage allowance (0-100) that triggers the alert.", - }), - name: z.string().optional().meta({ - description: - "Optional user-defined label to distinguish multiple alerts on the same feature.", - }), - }) - .check((ctx) => { - const { usage_threshold, usage_percentage_threshold } = ctx.value; - const hasUsage = usage_threshold !== undefined; - const hasPercentage = usage_percentage_threshold !== undefined; +export const UsageAlertThresholdType = z.enum([ + "usage_threshold", + "usage_percentage_threshold", +]); - if (!hasUsage && !hasPercentage) { - ctx.issues.push({ - code: "custom", - input: ctx.value, - message: - "At least one of usage_threshold or usage_percentage_threshold must be provided", - }); - return; - } - - if (hasUsage && hasPercentage) { - ctx.issues.push({ - code: "custom", - input: ctx.value, - message: - "Only one of usage_threshold or usage_percentage_threshold can be provided, not both", - }); - } - }); +export const DbUsageAlertSchema = z.object({ + feature_id: z.string().optional().meta({ + description: + "The feature ID this alert applies to. If omitted, the alert applies globally.", + }), + enabled: z.boolean().default(true).meta({ + description: "Whether this usage alert is enabled.", + }), + threshold: z.number().min(0).meta({ + description: + "The threshold value that triggers the alert. For usage_threshold, this is an absolute count. For usage_percentage_threshold, this is a percentage (0-100).", + }), + threshold_type: UsageAlertThresholdType.meta({ + description: + "Whether the threshold is an absolute usage count or a percentage of the usage allowance.", + }), + name: z.string().optional().meta({ + description: + "Optional user-defined label to distinguish multiple alerts on the same feature.", + }), +}); export type DbUsageAlert = z.infer; From 7eb24eb8c5d6d5ac8755d96797bc0dc1bb3ea717 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 11:50:39 +0000 Subject: [PATCH 06/49] add upper bound % check --- shared/api/billingControls/usageAlert.ts | 6 +++++- .../cusModels/billingControls/usageAlert.ts | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/shared/api/billingControls/usageAlert.ts b/shared/api/billingControls/usageAlert.ts index 3f49d069b..b6a3a7a05 100644 --- a/shared/api/billingControls/usageAlert.ts +++ b/shared/api/billingControls/usageAlert.ts @@ -1,6 +1,10 @@ import type { z } from "zod/v4"; -import { DbUsageAlertSchema } from "../../models/cusModels/billingControls/usageAlert.js"; +import { + DbUsageAlertSchema, + UsageAlertThresholdType, +} from "../../models/cusModels/billingControls/usageAlert.js"; export const ApiUsageAlertSchema = DbUsageAlertSchema; +export { UsageAlertThresholdType }; export type ApiUsageAlert = z.infer; diff --git a/shared/models/cusModels/billingControls/usageAlert.ts b/shared/models/cusModels/billingControls/usageAlert.ts index 8424cc787..f47fe5c29 100644 --- a/shared/models/cusModels/billingControls/usageAlert.ts +++ b/shared/models/cusModels/billingControls/usageAlert.ts @@ -5,7 +5,8 @@ export const UsageAlertThresholdType = z.enum([ "usage_percentage_threshold", ]); -export const DbUsageAlertSchema = z.object({ +export const DbUsageAlertSchema = z + .object({ feature_id: z.string().optional().meta({ description: "The feature ID this alert applies to. If omitted, the alert applies globally.", @@ -25,6 +26,19 @@ export const DbUsageAlertSchema = z.object({ description: "Optional user-defined label to distinguish multiple alerts on the same feature.", }), -}); + }) + .check((ctx) => { + const { threshold_type, threshold } = ctx.value; + + if (threshold_type === "usage_percentage_threshold" && threshold > 100) { + ctx.issues.push({ + code: "custom", + input: threshold, + path: ["threshold"], + message: + "Threshold must be between 0 and 100 for usage_percentage_threshold", + }); + } + }); export type DbUsageAlert = z.infer; From 28baddcb4c74f3821ae7b6f841d82d0dc66c1db2 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 12:07:23 +0000 Subject: [PATCH 07/49] define threshold_reached webhook schema --- .../api/webhooks/balancesThresholdReached.ts | 50 +++++++++++++++++++ shared/api/webhooks/index.ts | 1 + shared/enums/WebhookEventType.ts | 1 + shared/index.ts | 2 + 4 files changed, 54 insertions(+) create mode 100644 shared/api/webhooks/balancesThresholdReached.ts create mode 100644 shared/api/webhooks/index.ts diff --git a/shared/api/webhooks/balancesThresholdReached.ts b/shared/api/webhooks/balancesThresholdReached.ts new file mode 100644 index 000000000..7bd2e3698 --- /dev/null +++ b/shared/api/webhooks/balancesThresholdReached.ts @@ -0,0 +1,50 @@ +import { z } from "zod/v4"; +import { UsageAlertThresholdType } from "../billingControls/usageAlert.js"; + +export const BalancesThresholdType = z.enum([ + "usage_alert", + "allowance_used", + "limit_reached", +]); + +export const BalancesThresholdReachedUsageAlertSchema = z.object({ + name: z.string().optional().meta({ + description: "User-defined label for the alert, if provided.", + }), + threshold: z.number().meta({ + description: "The threshold value that was crossed.", + }), + threshold_type: UsageAlertThresholdType.meta({ + description: + "Whether the threshold is an absolute usage count or a percentage.", + }), + current_usage: z.number().meta({ + description: "The customer's usage at the time the alert was triggered.", + }), + current_balance: z.number().meta({ + description: "The customer's balance at the time the alert was triggered.", + }), +}); + +export const BalancesThresholdReachedSchema = z.object({ + customer_id: z.string().meta({ + description: "The ID of the customer whose threshold was reached.", + }), + feature_id: z.string().meta({ + description: "The feature ID the threshold applies to.", + }), + threshold_type: BalancesThresholdType.meta({ + description: "The type of threshold that was reached.", + }), + usage_alert: BalancesThresholdReachedUsageAlertSchema.optional().meta({ + description: + "Details of the usage alert that was triggered. Present when threshold_type is usage_alert.", + }), +}); + +export type BalancesThresholdReached = z.infer< + typeof BalancesThresholdReachedSchema +>; +export type BalancesThresholdReachedUsageAlert = z.infer< + typeof BalancesThresholdReachedUsageAlertSchema +>; diff --git a/shared/api/webhooks/index.ts b/shared/api/webhooks/index.ts new file mode 100644 index 000000000..402494dda --- /dev/null +++ b/shared/api/webhooks/index.ts @@ -0,0 +1 @@ +export * from "./balancesThresholdReached.js"; diff --git a/shared/enums/WebhookEventType.ts b/shared/enums/WebhookEventType.ts index 395860927..10b1f149c 100644 --- a/shared/enums/WebhookEventType.ts +++ b/shared/enums/WebhookEventType.ts @@ -1,4 +1,5 @@ export enum WebhookEventType { CustomerProductsUpdated = "customer.products.updated", CustomerThresholdReached = "customer.threshold_reached", + BalancesThresholdReached = "balances.threshold_reached", } diff --git a/shared/index.ts b/shared/index.ts index e977709da..5b8cd450f 100644 --- a/shared/index.ts +++ b/shared/index.ts @@ -31,6 +31,8 @@ export * from "./enums/LoggerAction"; // ENUMS export * from "./enums/SuccessCode"; export * from "./enums/WebhookEventType"; +// Webhook Schemas +export * from "./api/webhooks/index"; // Internal API (checkout app, dashboard) export * from "./internal/index"; // ANALYTICS MODELS From 9a547eeed3e24810fd964722c77749ff7008e941 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 12:28:55 +0000 Subject: [PATCH 08/49] remove balance from webhook payload --- shared/api/webhooks/balancesThresholdReached.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/shared/api/webhooks/balancesThresholdReached.ts b/shared/api/webhooks/balancesThresholdReached.ts index 7bd2e3698..d37a17b60 100644 --- a/shared/api/webhooks/balancesThresholdReached.ts +++ b/shared/api/webhooks/balancesThresholdReached.ts @@ -21,9 +21,6 @@ export const BalancesThresholdReachedUsageAlertSchema = z.object({ current_usage: z.number().meta({ description: "The customer's usage at the time the alert was triggered.", }), - current_balance: z.number().meta({ - description: "The customer's balance at the time the alert was triggered.", - }), }); export const BalancesThresholdReachedSchema = z.object({ From a5d4cf3b59c80c00df7c3744f3d0e3c78f410a6a Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 12:41:06 +0000 Subject: [PATCH 09/49] add check for usage alert --- .../api/webhooks/balancesThresholdReached.ts | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/shared/api/webhooks/balancesThresholdReached.ts b/shared/api/webhooks/balancesThresholdReached.ts index d37a17b60..9ee3e34e8 100644 --- a/shared/api/webhooks/balancesThresholdReached.ts +++ b/shared/api/webhooks/balancesThresholdReached.ts @@ -23,21 +23,46 @@ export const BalancesThresholdReachedUsageAlertSchema = z.object({ }), }); -export const BalancesThresholdReachedSchema = z.object({ - customer_id: z.string().meta({ - description: "The ID of the customer whose threshold was reached.", - }), - feature_id: z.string().meta({ - description: "The feature ID the threshold applies to.", - }), - threshold_type: BalancesThresholdType.meta({ - description: "The type of threshold that was reached.", - }), - usage_alert: BalancesThresholdReachedUsageAlertSchema.optional().meta({ - description: - "Details of the usage alert that was triggered. Present when threshold_type is usage_alert.", - }), -}); +export const BalancesThresholdReachedSchema = z + .object({ + customer_id: z.string().meta({ + description: "The ID of the customer whose threshold was reached.", + }), + feature_id: z.string().meta({ + description: "The feature ID the threshold applies to.", + }), + threshold_type: BalancesThresholdType.meta({ + description: "The type of threshold that was reached.", + }), + usage_alert: BalancesThresholdReachedUsageAlertSchema.optional().meta({ + description: + "Details of the usage alert that was triggered. Required when threshold_type is usage_alert.", + }), + }) + .check((ctx) => { + const { threshold_type, usage_alert } = ctx.value; + + if (threshold_type === "usage_alert" && !usage_alert) { + ctx.issues.push({ + code: "custom", + input: ctx.value, + message: + "usage_alert is required when threshold_type is usage_alert", + path: ["usage_alert"], + }); + return; + } + + if (threshold_type !== "usage_alert" && usage_alert) { + ctx.issues.push({ + code: "custom", + input: ctx.value, + message: + "usage_alert must not be provided when threshold_type is not usage_alert", + path: ["usage_alert"], + }); + } + }); export type BalancesThresholdReached = z.infer< typeof BalancesThresholdReachedSchema From bf1b3322030f6c5f3ae9a55e98a917e0ee607a23 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 12:51:34 +0000 Subject: [PATCH 10/49] remove_current_usage --- shared/api/webhooks/balancesThresholdReached.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/shared/api/webhooks/balancesThresholdReached.ts b/shared/api/webhooks/balancesThresholdReached.ts index 9ee3e34e8..9e32d48cd 100644 --- a/shared/api/webhooks/balancesThresholdReached.ts +++ b/shared/api/webhooks/balancesThresholdReached.ts @@ -18,9 +18,6 @@ export const BalancesThresholdReachedUsageAlertSchema = z.object({ description: "Whether the threshold is an absolute usage count or a percentage.", }), - current_usage: z.number().meta({ - description: "The customer's usage at the time the alert was triggered.", - }), }); export const BalancesThresholdReachedSchema = z From 538e5a92a57af9a7b2dcfc2d57b4ce925fc4e001 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 12:37:55 +0000 Subject: [PATCH 11/49] implement usage-alert detection and emission logic --- .../thresholdReached/checkUsageAlerts.ts | 109 ++++++++++++++++++ .../handleThresholdReached.ts | 0 .../deduction/executePostgresDeduction.ts | 14 ++- .../utils/deduction/executeRedisDeduction.ts | 14 ++- 4 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 server/src/internal/balances/thresholdReached/checkUsageAlerts.ts rename server/src/internal/balances/{utils => thresholdReached}/handleThresholdReached.ts (100%) diff --git a/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts b/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts new file mode 100644 index 000000000..bb50caa4f --- /dev/null +++ b/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts @@ -0,0 +1,109 @@ +import { + cusEntsToGrantedBalance, + cusEntsToUsage, + type DbUsageAlert, + type Feature, + type FullCustomer, + fullCustomerToCustomerEntitlements, + WebhookEventType, +} from "@autumn/shared"; +import { Decimal } from "decimal.js"; +import { sendSvixEvent } from "@/external/svix/svixHelpers.js"; +import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; + +const wasThresholdCrossed = ({ + alert, + oldUsage, + newUsage, + grantedBalance, +}: { + alert: DbUsageAlert; + oldUsage: number; + newUsage: number; + grantedBalance: number; +}) => { + if (alert.threshold_type === "usage_threshold") { + return oldUsage < alert.threshold && newUsage >= alert.threshold; + } + + // usage_percentage_threshold + if (grantedBalance <= 0) return false; + + const oldPercentage = new Decimal(oldUsage) + .div(grantedBalance) + .mul(100) + .toNumber(); + const newPercentage = new Decimal(newUsage) + .div(grantedBalance) + .mul(100) + .toNumber(); + + return oldPercentage < alert.threshold && newPercentage >= alert.threshold; +}; + +export const checkUsageAlerts = async ({ + ctx, + oldFullCus, + newFullCus, + feature, +}: { + ctx: AutumnContext; + oldFullCus: FullCustomer; + newFullCus: FullCustomer; + feature: Feature; +}) => { + const usageAlerts = newFullCus.usage_alerts; + if (!usageAlerts || usageAlerts.length === 0) return; + + const matchingAlerts = usageAlerts.filter( + (alert) => + alert.enabled && (alert.feature_id === feature.id || !alert.feature_id), + ); + + if (matchingAlerts.length === 0) return; + + const oldCustomerEntitlements = fullCustomerToCustomerEntitlements({ + fullCustomer: oldFullCus, + featureId: feature.id, + }); + + const newCustomerEntitlements = fullCustomerToCustomerEntitlements({ + fullCustomer: newFullCus, + featureId: feature.id, + }); + + const oldUsage = cusEntsToUsage({ cusEnts: oldCustomerEntitlements }); + const newUsage = cusEntsToUsage({ cusEnts: newCustomerEntitlements }); + + const grantedBalance = cusEntsToGrantedBalance({ + cusEnts: newCustomerEntitlements, + }); + + for (const alert of matchingAlerts) { + if (!wasThresholdCrossed({ alert, oldUsage, newUsage, grantedBalance })) + continue; + + const customerId = newFullCus.id || newFullCus.internal_id; + + await sendSvixEvent({ + org: ctx.org, + env: ctx.env, + eventType: WebhookEventType.BalancesThresholdReached, + data: { + customer_id: customerId, + feature_id: feature.id, + threshold_type: "usage_alert", + usage_alert: { + name: alert.name, + threshold: alert.threshold, + threshold_type: alert.threshold_type, + current_usage: newUsage, + }, + }, + }); + + ctx.logger.info( + `Usage alert triggered for customer ${customerId}, feature ${feature.id}, threshold ${alert.threshold} (${alert.threshold_type})`, + ); + } +}; diff --git a/server/src/internal/balances/utils/handleThresholdReached.ts b/server/src/internal/balances/thresholdReached/handleThresholdReached.ts similarity index 100% rename from server/src/internal/balances/utils/handleThresholdReached.ts rename to server/src/internal/balances/thresholdReached/handleThresholdReached.ts diff --git a/server/src/internal/balances/utils/deduction/executePostgresDeduction.ts b/server/src/internal/balances/utils/deduction/executePostgresDeduction.ts index f11e6447d..23d29e3f8 100644 --- a/server/src/internal/balances/utils/deduction/executePostgresDeduction.ts +++ b/server/src/internal/balances/utils/deduction/executePostgresDeduction.ts @@ -16,7 +16,8 @@ import type { DeductionUpdate } from "../../utils/types/deductionUpdate.js"; import type { FeatureDeduction } from "../../utils/types/featureDeduction.js"; import type { MutationLogItem } from "../../utils/types/mutationLogItem.js"; import { createAllocatedInvoice } from "../allocatedInvoice/createAllocatedInvoice.js"; -import { handleThresholdReached } from "../handleThresholdReached.js"; +import { checkUsageAlerts } from "../../thresholdReached/checkUsageAlerts.js"; +import { handleThresholdReached } from "../../thresholdReached/handleThresholdReached.js"; import type { DeductionOptions } from "../types/deductionTypes.js"; import { applyRolloverUpdatesToFullCustomer } from "./applyRolloverUpdatesToFullCustomer.js"; import { @@ -239,6 +240,17 @@ export const executePostgresDeduction = async ({ ); }); + checkUsageAlerts({ + ctx, + oldFullCus, + newFullCus: fullCustomer, + feature: deduction.feature, + }).catch((error) => { + ctx.logger.error( + `[executePostgresDeduction] Failed to check usage alerts: ${error}`, + ); + }); + if (resolvedOptions.triggerAutoTopUp) { triggerAutoTopUp({ ctx, diff --git a/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts b/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts index 007810f18..c37f6fc10 100644 --- a/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts +++ b/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts @@ -10,7 +10,8 @@ import { handlePaidAllocatedCusEnt } from "@/internal/balances/utils/paidAllocat import { rollbackDeduction } from "@/internal/balances/utils/paidAllocatedFeature/rollbackDeduction.js"; import { buildFullCustomerCacheKey } from "@/internal/customers/cusUtils/fullCustomerCacheUtils/fullCustomerCacheConfig.js"; import { tryRedisWrite } from "@/utils/cacheUtils/cacheUtils.js"; -import { handleThresholdReached } from "../handleThresholdReached.js"; +import { checkUsageAlerts } from "../../thresholdReached/checkUsageAlerts.js"; +import { handleThresholdReached } from "../../thresholdReached/handleThresholdReached.js"; import type { DeductionOptions } from "../types/deductionTypes.js"; import type { DeductionUpdate } from "../types/deductionUpdate.js"; import type { FeatureDeduction } from "../types/featureDeduction.js"; @@ -246,6 +247,17 @@ export const executeRedisDeduction = async ({ ); }); + checkUsageAlerts({ + ctx, + oldFullCus, + newFullCus: fullCustomer, + feature: deduction.feature, + }).catch((error) => { + ctx.logger.error( + `[executeRedisDeduction] Failed to check usage alerts: ${error}`, + ); + }); + if (options.triggerAutoTopUp) { triggerAutoTopUp({ ctx, From 7de45d6c0b22bb7d7fa9fcdfdcea20f75f769d8b Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 13:08:20 +0000 Subject: [PATCH 12/49] use oldGrantedBalance and newGrantedBalance --- .../thresholdReached/checkUsageAlerts.ts | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts b/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts index bb50caa4f..7f3a05fb9 100644 --- a/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts +++ b/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts @@ -15,26 +15,28 @@ const wasThresholdCrossed = ({ alert, oldUsage, newUsage, - grantedBalance, + oldGrantedBalance, + newGrantedBalance, }: { alert: DbUsageAlert; oldUsage: number; newUsage: number; - grantedBalance: number; + oldGrantedBalance: number; + newGrantedBalance: number; }) => { if (alert.threshold_type === "usage_threshold") { return oldUsage < alert.threshold && newUsage >= alert.threshold; } // usage_percentage_threshold - if (grantedBalance <= 0) return false; + if (oldGrantedBalance <= 0 || newGrantedBalance <= 0) return false; const oldPercentage = new Decimal(oldUsage) - .div(grantedBalance) + .div(oldGrantedBalance) .mul(100) .toNumber(); const newPercentage = new Decimal(newUsage) - .div(grantedBalance) + .div(newGrantedBalance) .mul(100) .toNumber(); @@ -75,12 +77,23 @@ export const checkUsageAlerts = async ({ const oldUsage = cusEntsToUsage({ cusEnts: oldCustomerEntitlements }); const newUsage = cusEntsToUsage({ cusEnts: newCustomerEntitlements }); - const grantedBalance = cusEntsToGrantedBalance({ + const oldGrantedBalance = cusEntsToGrantedBalance({ + cusEnts: oldCustomerEntitlements, + }); + const newGrantedBalance = cusEntsToGrantedBalance({ cusEnts: newCustomerEntitlements, }); for (const alert of matchingAlerts) { - if (!wasThresholdCrossed({ alert, oldUsage, newUsage, grantedBalance })) + if ( + !wasThresholdCrossed({ + alert, + oldUsage, + newUsage, + oldGrantedBalance, + newGrantedBalance, + }) + ) continue; const customerId = newFullCus.id || newFullCus.internal_id; From 9973aa078e8d9180bc19582076b3fc5217dcb0f8 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 15:44:38 +0000 Subject: [PATCH 13/49] prepaid fix --- .../thresholdReached/checkUsageAlerts.ts | 18 ++++++++++++------ .../customers/actions/update/updateCustomer.ts | 1 + 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts b/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts index 7f3a05fb9..3fdf25f5a 100644 --- a/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts +++ b/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts @@ -1,5 +1,6 @@ import { cusEntsToGrantedBalance, + cusEntsToPrepaidQuantity, cusEntsToUsage, type DbUsageAlert, type Feature, @@ -77,12 +78,17 @@ export const checkUsageAlerts = async ({ const oldUsage = cusEntsToUsage({ cusEnts: oldCustomerEntitlements }); const newUsage = cusEntsToUsage({ cusEnts: newCustomerEntitlements }); - const oldGrantedBalance = cusEntsToGrantedBalance({ - cusEnts: oldCustomerEntitlements, - }); - const newGrantedBalance = cusEntsToGrantedBalance({ - cusEnts: newCustomerEntitlements, - }); + const oldGrantedBalance = new Decimal( + cusEntsToGrantedBalance({ cusEnts: oldCustomerEntitlements }), + ) + .add(cusEntsToPrepaidQuantity({ cusEnts: oldCustomerEntitlements })) + .toNumber(); + + const newGrantedBalance = new Decimal( + cusEntsToGrantedBalance({ cusEnts: newCustomerEntitlements }), + ) + .add(cusEntsToPrepaidQuantity({ cusEnts: newCustomerEntitlements })) + .toNumber(); for (const alert of matchingAlerts) { if ( diff --git a/server/src/internal/customers/actions/update/updateCustomer.ts b/server/src/internal/customers/actions/update/updateCustomer.ts index 21a65ea5a..06e5db716 100644 --- a/server/src/internal/customers/actions/update/updateCustomer.ts +++ b/server/src/internal/customers/actions/update/updateCustomer.ts @@ -114,6 +114,7 @@ export const updateCustomer = async ({ ...(billing_controls && { auto_topups: billing_controls.auto_topups, spend_limits: billing_controls.spend_limits, + usage_alerts: billing_controls.usage_alerts, }), }; From 023a1c67e3da8d95fd28427f0249c417249f57fc Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 13:21:40 +0000 Subject: [PATCH 14/49] use new usage alert webhook --- .../thresholdReached/checkUsageAlerts.ts | 1 - .../handleThresholdReached.ts | 26 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts b/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts index 3fdf25f5a..933f253c1 100644 --- a/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts +++ b/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts @@ -116,7 +116,6 @@ export const checkUsageAlerts = async ({ name: alert.name, threshold: alert.threshold, threshold_type: alert.threshold_type, - current_usage: newUsage, }, }, }); diff --git a/server/src/internal/balances/thresholdReached/handleThresholdReached.ts b/server/src/internal/balances/thresholdReached/handleThresholdReached.ts index 21ae90224..2a322e004 100644 --- a/server/src/internal/balances/thresholdReached/handleThresholdReached.ts +++ b/server/src/internal/balances/thresholdReached/handleThresholdReached.ts @@ -83,6 +83,8 @@ const handleAllowanceUsed = async ({ }); if (oldAllowed === true && newAllowed === false) { + const customerId = newFullCus.id || newFullCus.internal_id; + await sendSvixEvent({ org: ctx.org, env: ctx.env, @@ -101,6 +103,17 @@ const handleAllowanceUsed = async ({ }), }, }); + + await sendSvixEvent({ + org: ctx.org, + env: ctx.env, + eventType: WebhookEventType.BalancesThresholdReached, + data: { + customer_id: customerId, + feature_id: feature.id, + threshold_type: "allowance_used", + }, + }); } }; @@ -154,6 +167,8 @@ export const handleThresholdReached = async ({ }); if (oldAllowed === true && newAllowed === false) { + const customerId = newFullCus.id || newFullCus.internal_id; + await sendSvixEvent({ org: ctx.org, env: ctx.env, @@ -173,6 +188,17 @@ export const handleThresholdReached = async ({ }, }); + await sendSvixEvent({ + org: ctx.org, + env: ctx.env, + eventType: WebhookEventType.BalancesThresholdReached, + data: { + customer_id: customerId, + feature_id: feature.id, + threshold_type: "limit_reached", + }, + }); + ctx.logger.info( "Sent Svix event for threshold reached (type: limit_reached)", ); From ec212410468d559d0e31613ea968361b37f3e715 Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 15:41:40 +0000 Subject: [PATCH 15/49] add unit tests --- .../usage-alerts/usage-alert-basic.test.ts | 428 ++++++++++++++++++ .../customerUsageAlertUtils.ts | 20 + .../svixUsageAlertEndpoint.ts | 55 +++ 3 files changed, 503 insertions(+) create mode 100644 server/tests/integration/balances/track/usage-alerts/usage-alert-basic.test.ts create mode 100644 server/tests/integration/balances/utils/usage-alert-utils/customerUsageAlertUtils.ts create mode 100644 server/tests/integration/balances/utils/usage-alert-utils/svixUsageAlertEndpoint.ts diff --git a/server/tests/integration/balances/track/usage-alerts/usage-alert-basic.test.ts b/server/tests/integration/balances/track/usage-alerts/usage-alert-basic.test.ts new file mode 100644 index 000000000..04cfec5a9 --- /dev/null +++ b/server/tests/integration/balances/track/usage-alerts/usage-alert-basic.test.ts @@ -0,0 +1,428 @@ +/** + * Integration tests for usage alert webhooks. + * + * Verifies that `balances.threshold_reached` webhooks fire correctly when + * customer usage crosses configured thresholds (both absolute and percentage). + * + * Uses Svix Play to receive and verify webhooks. + */ + +import { afterAll, beforeAll, expect, test } from "bun:test"; +import { + generatePlayToken, + getPlayWebhookUrl, + waitForWebhook, +} from "@tests/integration/billing/autumn-webhooks/utils/svixPlayClient.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import ctx from "@tests/utils/testInitUtils/createTestContext.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { setCustomerUsageAlerts } from "../../utils/usage-alert-utils/customerUsageAlertUtils.js"; +import { + createUsageAlertTestEndpoint, + deleteUsageAlertTestEndpoint, +} from "../../utils/usage-alert-utils/svixUsageAlertEndpoint.js"; + +type BalancesThresholdReachedPayload = { + type: string; + data: { + customer_id: string; + feature_id: string; + threshold_type: string; + usage_alert?: { + name?: string; + threshold: number; + threshold_type: string; + }; + }; +}; + +// ═══════════════════════════════════════════════════════════════════════════════ +// SVIX PLAY SETUP +// ═══════════════════════════════════════════════════════════════════════════════ + +let playToken: string; +let endpointId: string; + +beforeAll(async () => { + playToken = await generatePlayToken(); + console.log(`Generated Svix Play token: ${playToken}`); + + const svixAppId = ctx.org.svix_config?.sandbox_app_id; + if (!svixAppId) { + throw new Error( + "Test org does not have svix_config.sandbox_app_id configured. " + + "Cannot run webhook integration tests without Svix app.", + ); + } + + const playUrl = getPlayWebhookUrl(playToken); + console.log(`Creating Svix endpoint: ${playUrl}`); + endpointId = await createUsageAlertTestEndpoint({ + appId: svixAppId, + playUrl, + }); + console.log(`Created Svix endpoint: ${endpointId}`); +}); + +afterAll(async () => { + const svixAppId = ctx.org.svix_config?.sandbox_app_id; + if (svixAppId && endpointId) { + await deleteUsageAlertTestEndpoint({ appId: svixAppId, endpointId }); + console.log(`Deleted Svix endpoint: ${endpointId}`); + } +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 1: Usage threshold crossing triggers webhook +// ═══════════════════════════════════════════════════════════════════════════════ + +test(`${chalk.yellowBright("usage-alert1: usage threshold crossing triggers webhook")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 1000 }); + const freeProd = products.base({ + id: "ua-threshold-1", + items: [messagesItem], + }); + + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "usage-alert-threshold-1", + setup: [s.customer({ testClock: false }), s.products({ list: [freeProd] })], + actions: [s.attach({ productId: freeProd.id })], + }); + + await setCustomerUsageAlerts({ + autumn: autumnV2_1, + customerId, + usageAlerts: [ + { + feature_id: TestFeature.Messages, + threshold: 800, + threshold_type: "usage_threshold", + enabled: true, + }, + ], + }); + + // Track 850 usage — crosses threshold of 800 + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 850, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.threshold_reached" && + payload.data?.customer_id === customerId && + payload.data?.threshold_type === "usage_alert" && + payload.data?.usage_alert?.threshold === 800, + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + expect(result?.payload.type).toBe("balances.threshold_reached"); + + const { data } = result!.payload; + expect(data.customer_id).toBe(customerId); + expect(data.feature_id).toBe(TestFeature.Messages); + expect(data.threshold_type).toBe("usage_alert"); + expect(data.usage_alert).toBeDefined(); + expect(data.usage_alert!.threshold).toBe(800); + expect(data.usage_alert!.threshold_type).toBe("usage_threshold"); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 2: Usage percentage threshold crossing triggers webhook +// ═══════════════════════════════════════════════════════════════════════════════ + +test(`${chalk.yellowBright("usage-alert2: percentage threshold crossing triggers webhook")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 1000 }); + const freeProd = products.base({ + id: "ua-pct-1", + items: [messagesItem], + }); + + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "usage-alert-pct-1", + setup: [s.customer({ testClock: false }), s.products({ list: [freeProd] })], + actions: [s.attach({ productId: freeProd.id })], + }); + + await setCustomerUsageAlerts({ + autumn: autumnV2_1, + customerId, + usageAlerts: [ + { + feature_id: TestFeature.Messages, + threshold: 90, + threshold_type: "usage_percentage_threshold", + enabled: true, + }, + ], + }); + + // Track 950 usage — 95% of 1000 allowance, crosses 90% threshold + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 950, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.threshold_reached" && + payload.data?.customer_id === customerId && + payload.data?.threshold_type === "usage_alert" && + payload.data?.usage_alert?.threshold === 90, + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + expect(result?.payload.type).toBe("balances.threshold_reached"); + + const { data } = result!.payload; + expect(data.customer_id).toBe(customerId); + expect(data.feature_id).toBe(TestFeature.Messages); + expect(data.threshold_type).toBe("usage_alert"); + expect(data.usage_alert).toBeDefined(); + expect(data.usage_alert!.threshold).toBe(90); + expect(data.usage_alert!.threshold_type).toBe("usage_percentage_threshold"); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 3: Alert does not re-fire after already crossed +// ═══════════════════════════════════════════════════════════════════════════════ + +test(`${chalk.yellowBright("usage-alert3: alert does not re-fire after already crossed")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 1000 }); + const freeProd = products.base({ + id: "ua-no-refire-1", + items: [messagesItem], + }); + + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "usage-alert-no-refire-1", + setup: [s.customer({ testClock: false }), s.products({ list: [freeProd] })], + actions: [s.attach({ productId: freeProd.id })], + }); + + await setCustomerUsageAlerts({ + autumn: autumnV2_1, + customerId, + usageAlerts: [ + { + feature_id: TestFeature.Messages, + threshold: 500, + threshold_type: "usage_threshold", + enabled: true, + }, + ], + }); + + // First track: 600 usage — crosses threshold of 500, expect webhook + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 600, + }); + + const firstResult = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.threshold_reached" && + payload.data?.customer_id === customerId && + payload.data?.usage_alert?.threshold === 500, + timeoutMs: 15000, + }); + + expect(firstResult).not.toBeNull(); + + // Second track: 100 more usage — already crossed, should NOT fire again + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 100, + }); + + // Wait and check — should NOT find a second webhook + // (waitForWebhook returns all history, so we count matching events) + await timeout(5000); + + let matchCount = 0; + const { getPlayHistory, parseEventBody } = await import( + "@tests/integration/billing/autumn-webhooks/utils/svixPlayClient.js" + ); + const history = await getPlayHistory({ token: playToken }); + for (const event of history.data) { + try { + const payload = parseEventBody(event); + if ( + payload.type === "balances.threshold_reached" && + payload.data?.customer_id === customerId && + payload.data?.usage_alert?.threshold === 500 + ) { + matchCount++; + } + } catch { + // Skip unparseable events + } + } + + // Should have exactly 1 webhook, not 2 + expect(matchCount).toBe(1); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 4: Disabled alert does not fire +// ═══════════════════════════════════════════════════════════════════════════════ + +test(`${chalk.yellowBright("usage-alert4: disabled alert does not fire")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 1000 }); + const freeProd = products.base({ + id: "ua-disabled-1", + items: [messagesItem], + }); + + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "usage-alert-disabled-1", + setup: [s.customer({ testClock: false }), s.products({ list: [freeProd] })], + actions: [s.attach({ productId: freeProd.id })], + }); + + await setCustomerUsageAlerts({ + autumn: autumnV2_1, + customerId, + usageAlerts: [ + { + feature_id: TestFeature.Messages, + threshold: 500, + threshold_type: "usage_threshold", + enabled: false, + }, + ], + }); + + // Track 600 — crosses threshold, but alert is disabled + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 600, + }); + + // Wait and verify no webhook + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.threshold_reached" && + payload.data?.customer_id === customerId && + payload.data?.usage_alert?.threshold === 500, + timeoutMs: 8000, + }); + + expect(result).toBeNull(); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 5: Multiple alerts fire independently +// ═══════════════════════════════════════════════════════════════════════════════ + +test(`${chalk.yellowBright("usage-alert5: multiple alerts fire independently")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 1000 }); + const freeProd = products.base({ + id: "ua-multi-1", + items: [messagesItem], + }); + + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "usage-alert-multi-1", + setup: [s.customer({ testClock: false }), s.products({ list: [freeProd] })], + actions: [s.attach({ productId: freeProd.id })], + }); + + await setCustomerUsageAlerts({ + autumn: autumnV2_1, + customerId, + usageAlerts: [ + { + feature_id: TestFeature.Messages, + threshold: 500, + threshold_type: "usage_threshold", + enabled: true, + }, + { + feature_id: TestFeature.Messages, + threshold: 800, + threshold_type: "usage_threshold", + enabled: true, + }, + ], + }); + + // Track 600 — crosses 500 but not 800 + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 600, + }); + + const firstResult = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.threshold_reached" && + payload.data?.customer_id === customerId && + payload.data?.usage_alert?.threshold === 500, + timeoutMs: 15000, + }); + + expect(firstResult).not.toBeNull(); + expect(firstResult!.payload.data.usage_alert!.threshold).toBe(500); + + // Verify 800 threshold has NOT fired yet + await timeout(3000); + + const { getPlayHistory, parseEventBody } = await import( + "@tests/integration/billing/autumn-webhooks/utils/svixPlayClient.js" + ); + + let has800 = false; + const historyMid = await getPlayHistory({ token: playToken }); + for (const event of historyMid.data) { + try { + const payload = parseEventBody(event); + if ( + payload.type === "balances.threshold_reached" && + payload.data?.customer_id === customerId && + payload.data?.usage_alert?.threshold === 800 + ) { + has800 = true; + } + } catch { + // Skip + } + } + expect(has800).toBe(false); + + // Track 300 more (total 900) — crosses 800 threshold + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 300, + }); + + const secondResult = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.threshold_reached" && + payload.data?.customer_id === customerId && + payload.data?.usage_alert?.threshold === 800, + timeoutMs: 15000, + }); + + expect(secondResult).not.toBeNull(); + expect(secondResult!.payload.data.usage_alert!.threshold).toBe(800); +}); diff --git a/server/tests/integration/balances/utils/usage-alert-utils/customerUsageAlertUtils.ts b/server/tests/integration/balances/utils/usage-alert-utils/customerUsageAlertUtils.ts new file mode 100644 index 000000000..aa860d29c --- /dev/null +++ b/server/tests/integration/balances/utils/usage-alert-utils/customerUsageAlertUtils.ts @@ -0,0 +1,20 @@ +import type { CustomerBillingControls, DbUsageAlert } from "@autumn/shared"; +import type { AutumnV2_1Client } from "../spend-limit-utils/entitySpendLimitUtils.js"; + +export const setCustomerUsageAlerts = async ({ + autumn, + customerId, + usageAlerts, +}: { + autumn: AutumnV2_1Client; + customerId: string; + usageAlerts: DbUsageAlert[]; +}) => { + const billingControls: CustomerBillingControls = { + usage_alerts: usageAlerts, + }; + + await autumn.customers.update(customerId, { + billing_controls: billingControls, + }); +}; diff --git a/server/tests/integration/balances/utils/usage-alert-utils/svixUsageAlertEndpoint.ts b/server/tests/integration/balances/utils/usage-alert-utils/svixUsageAlertEndpoint.ts new file mode 100644 index 000000000..911a4e031 --- /dev/null +++ b/server/tests/integration/balances/utils/usage-alert-utils/svixUsageAlertEndpoint.ts @@ -0,0 +1,55 @@ +/** + * Svix endpoint utilities for usage alert webhook tests. + * Creates temporary endpoints filtered to `balances.threshold_reached` events. + */ + +import { Svix } from "svix"; + +let svixClient: Svix | null = null; + +const getSvixClient = (): Svix => { + if (!svixClient) { + const apiKey = process.env.SVIX_API_KEY; + if (!apiKey) { + throw new Error( + "SVIX_API_KEY environment variable is required for webhook tests", + ); + } + svixClient = new Svix(apiKey); + } + return svixClient; +}; + +export const createUsageAlertTestEndpoint = async ({ + appId, + playUrl, +}: { + appId: string; + playUrl: string; +}): Promise => { + const svix = getSvixClient(); + + const endpoint = await svix.endpoint.create(appId, { + url: playUrl, + description: "Test endpoint for usage alert webhook tests", + filterTypes: ["balances.threshold_reached"], + }); + + return endpoint.id; +}; + +export const deleteUsageAlertTestEndpoint = async ({ + appId, + endpointId, +}: { + appId: string; + endpointId: string; +}): Promise => { + const svix = getSvixClient(); + + try { + await svix.endpoint.delete(appId, endpointId); + } catch (error) { + console.warn(`Failed to delete test endpoint ${endpointId}:`, error); + } +}; From aad864470617ce349e37375f742256ee46a6e12a Mon Sep 17 00:00:00 2001 From: Owen Greenhalgh Date: Thu, 19 Mar 2026 16:40:42 +0000 Subject: [PATCH 16/49] address comments --- .../handleThresholdReached.ts | 118 +++++++++--------- .../usage-alerts/usage-alert-basic.test.ts | 23 ++-- 2 files changed, 73 insertions(+), 68 deletions(-) diff --git a/server/src/internal/balances/thresholdReached/handleThresholdReached.ts b/server/src/internal/balances/thresholdReached/handleThresholdReached.ts index 2a322e004..87e628148 100644 --- a/server/src/internal/balances/thresholdReached/handleThresholdReached.ts +++ b/server/src/internal/balances/thresholdReached/handleThresholdReached.ts @@ -85,35 +85,36 @@ const handleAllowanceUsed = async ({ if (oldAllowed === true && newAllowed === false) { const customerId = newFullCus.id || newFullCus.internal_id; - await sendSvixEvent({ - org: ctx.org, - env: ctx.env, - eventType: WebhookEventType.CustomerThresholdReached, - data: { - threshold_type: "allowance_used", - customer: cleanApiCustomer({ - ctx, - apiCustomer: newApiCustomer, - legacyData: newLegacyData, - }), - feature: dbToApiFeatureV1({ - ctx, - dbFeature: feature, - targetVersion: ctx.apiVersion, - }), - }, - }); - - await sendSvixEvent({ - org: ctx.org, - env: ctx.env, - eventType: WebhookEventType.BalancesThresholdReached, - data: { - customer_id: customerId, - feature_id: feature.id, - threshold_type: "allowance_used", - }, - }); + await Promise.all([ + sendSvixEvent({ + org: ctx.org, + env: ctx.env, + eventType: WebhookEventType.CustomerThresholdReached, + data: { + threshold_type: "allowance_used", + customer: cleanApiCustomer({ + ctx, + apiCustomer: newApiCustomer, + legacyData: newLegacyData, + }), + feature: dbToApiFeatureV1({ + ctx, + dbFeature: feature, + targetVersion: ctx.apiVersion, + }), + }, + }), + sendSvixEvent({ + org: ctx.org, + env: ctx.env, + eventType: WebhookEventType.BalancesThresholdReached, + data: { + customer_id: customerId, + feature_id: feature.id, + threshold_type: "allowance_used", + }, + }), + ]); } }; @@ -169,35 +170,36 @@ export const handleThresholdReached = async ({ if (oldAllowed === true && newAllowed === false) { const customerId = newFullCus.id || newFullCus.internal_id; - await sendSvixEvent({ - org: ctx.org, - env: ctx.env, - eventType: WebhookEventType.CustomerThresholdReached, - data: { - threshold_type: "limit_reached", - customer: cleanApiCustomer({ - ctx, - apiCustomer: newApiCustomer, - legacyData: newLegacyData, - }), - feature: dbToApiFeatureV1({ - ctx, - dbFeature: feature, - targetVersion: ctx.apiVersion, - }), - }, - }); - - await sendSvixEvent({ - org: ctx.org, - env: ctx.env, - eventType: WebhookEventType.BalancesThresholdReached, - data: { - customer_id: customerId, - feature_id: feature.id, - threshold_type: "limit_reached", - }, - }); + await Promise.all([ + sendSvixEvent({ + org: ctx.org, + env: ctx.env, + eventType: WebhookEventType.CustomerThresholdReached, + data: { + threshold_type: "limit_reached", + customer: cleanApiCustomer({ + ctx, + apiCustomer: newApiCustomer, + legacyData: newLegacyData, + }), + feature: dbToApiFeatureV1({ + ctx, + dbFeature: feature, + targetVersion: ctx.apiVersion, + }), + }, + }), + sendSvixEvent({ + org: ctx.org, + env: ctx.env, + eventType: WebhookEventType.BalancesThresholdReached, + data: { + customer_id: customerId, + feature_id: feature.id, + threshold_type: "limit_reached", + }, + }), + ]); ctx.logger.info( "Sent Svix event for threshold reached (type: limit_reached)", diff --git a/server/tests/integration/balances/track/usage-alerts/usage-alert-basic.test.ts b/server/tests/integration/balances/track/usage-alerts/usage-alert-basic.test.ts index 04cfec5a9..72a72f76f 100644 --- a/server/tests/integration/balances/track/usage-alerts/usage-alert-basic.test.ts +++ b/server/tests/integration/balances/track/usage-alerts/usage-alert-basic.test.ts @@ -10,7 +10,9 @@ import { afterAll, beforeAll, expect, test } from "bun:test"; import { generatePlayToken, + getPlayHistory, getPlayWebhookUrl, + parseEventBody, waitForWebhook, } from "@tests/integration/billing/autumn-webhooks/utils/svixPlayClient.js"; import { TestFeature } from "@tests/setup/v2Features.js"; @@ -249,14 +251,19 @@ test(`${chalk.yellowBright("usage-alert3: alert does not re-fire after already c value: 100, }); - // Wait and check — should NOT find a second webhook - // (waitForWebhook returns all history, so we count matching events) - await timeout(5000); + // Wait briefly, then assert no second webhook arrived + await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.threshold_reached" && + payload.data?.customer_id === customerId && + payload.data?.usage_alert?.threshold === 500, + timeoutMs: 8000, + }); + // waitForWebhook scans all history — if it finds a match it's the same first one. + // Count total matches to confirm only 1 exists. let matchCount = 0; - const { getPlayHistory, parseEventBody } = await import( - "@tests/integration/billing/autumn-webhooks/utils/svixPlayClient.js" - ); const history = await getPlayHistory({ token: playToken }); for (const event of history.data) { try { @@ -385,10 +392,6 @@ test(`${chalk.yellowBright("usage-alert5: multiple alerts fire independently")}` // Verify 800 threshold has NOT fired yet await timeout(3000); - const { getPlayHistory, parseEventBody } = await import( - "@tests/integration/billing/autumn-webhooks/utils/svixPlayClient.js" - ); - let has800 = false; const historyMid = await getPlayHistory({ token: playToken }); for (const event of historyMid.data) { From 9e3a653c96e27f5dbf31d57a8147ee958a26071c Mon Sep 17 00:00:00 2001 From: John Yeo Date: Sat, 21 Mar 2026 13:08:52 +0000 Subject: [PATCH 17/49] fix: is free product --- .../billing/v2/utils/logs/logAutumnBillingPlan.ts | 13 +++++++++---- .../classifyCustomerProduct.ts | 6 +++++- .../classifyProduct/classifyProductUtils.ts | 8 ++++---- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/server/src/internal/billing/v2/utils/logs/logAutumnBillingPlan.ts b/server/src/internal/billing/v2/utils/logs/logAutumnBillingPlan.ts index 88cee9dc7..f5d15b4d0 100644 --- a/server/src/internal/billing/v2/utils/logs/logAutumnBillingPlan.ts +++ b/server/src/internal/billing/v2/utils/logs/logAutumnBillingPlan.ts @@ -1,4 +1,8 @@ -import type { AutumnBillingPlan, BillingContext } from "@autumn/shared"; +import { + type AutumnBillingPlan, + type BillingContext, + formatMs, +} from "@autumn/shared"; import type { AutumnContext } from "@/honoUtils/HonoEnv"; import { getTrialStateTransition } from "@/internal/billing/v2/utils/billingContext/getTrialStateTransition"; import { addToExtraLogs } from "@/utils/logging/addToExtraLogs"; @@ -56,9 +60,10 @@ export const logAutumnBillingPlan = ({ .join(", ") || "none", lineItems: - plan.lineItems?.map( - (item) => `${item.description}: ${item.amountAfterDiscounts}`, - ) ?? "none", + plan.lineItems?.map((item) => ({ + item: `${item.description}: ${item.amountAfterDiscounts}`, + effectivePeriod: `${formatMs(item.context.effectivePeriod?.start)} - ${formatMs(item.context.effectivePeriod?.end)}`, + })) ?? "none", }, }, }); diff --git a/shared/utils/cusProductUtils/classifyCustomerProduct/classifyCustomerProduct.ts b/shared/utils/cusProductUtils/classifyCustomerProduct/classifyCustomerProduct.ts index 418d20bcf..bf09b7abf 100644 --- a/shared/utils/cusProductUtils/classifyCustomerProduct/classifyCustomerProduct.ts +++ b/shared/utils/cusProductUtils/classifyCustomerProduct/classifyCustomerProduct.ts @@ -65,7 +65,11 @@ export const isCustomerProductPaidRecurring = ( ) => { if (!customerProduct) return false; const prices = cusProductToPrices({ cusProduct: customerProduct }); - return !isFreeProduct({ prices }) && !isOneOffProduct({ prices }); + + const freeProduct = isFreeProduct({ prices }); + const oneOffProduct = isOneOffProduct({ prices }); + + return !freeProduct && !oneOffProduct; }; // ============================================================================ diff --git a/shared/utils/productUtils/classifyProduct/classifyProductUtils.ts b/shared/utils/productUtils/classifyProduct/classifyProductUtils.ts index 44bbb85f5..f7e3d88d6 100644 --- a/shared/utils/productUtils/classifyProduct/classifyProductUtils.ts +++ b/shared/utils/productUtils/classifyProduct/classifyProductUtils.ts @@ -1,5 +1,5 @@ import { BillingInterval } from "@models/productModels/intervals/billingInterval.js"; -import { type FullProduct, nullish } from "../../../index.js"; +import { type FullProduct, notNullish, nullish } from "../../../index.js"; import type { FixedPriceConfig } from "../../../models/productModels/priceModels/priceConfig/fixedPriceConfig.js"; import type { UsagePriceConfig } from "../../../models/productModels/priceModels/priceConfig/usagePriceConfig.js"; import { PriceType } from "../../../models/productModels/priceModels/priceEnums.js"; @@ -29,15 +29,15 @@ export const isFreeProduct = ({ prices }: { prices: Price[] }) => { let totalPrice = 0; for (const price of prices) { - if ("usage_tiers" in price.config) { + if ("usage_tiers" in price.config && notNullish(price.config.usage_tiers)) { const tiers = price.config.usage_tiers; if (nullish(tiers) || tiers.length === 0) continue; totalPrice += tiers.reduce( (acc, tier) => acc + tier.amount + (tier.flat_amount ?? 0), 0, ); - } else { - totalPrice += price.config.amount; + } else if ("amount" in price.config && notNullish(price.config.amount)) { + totalPrice += price.config.amount ?? 0; } } return totalPrice === 0; From abbb14dae00d34b2088f6da1c895b29ad033744c Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Sat, 21 Mar 2026 21:13:01 +0000 Subject: [PATCH 18/49] docs: fix auto top-up dashboard instructions and remove duplicate setup code Made-with: Cursor --- .../documentation/getting-started/setup.mdx | 55 ------------------- .../modelling-pricing/auto-top-ups.mdx | 16 ++++-- 2 files changed, 10 insertions(+), 61 deletions(-) diff --git a/apps/docs/mintlify/documentation/getting-started/setup.mdx b/apps/docs/mintlify/documentation/getting-started/setup.mdx index e4cb0a1d1..94e222f21 100644 --- a/apps/docs/mintlify/documentation/getting-started/setup.mdx +++ b/apps/docs/mintlify/documentation/getting-started/setup.mdx @@ -128,61 +128,6 @@ app.use( import { autumnHandler } from "autumn-js/webStandard"; import { Elysia } from "elysia"; -const app = new Elysia() - .mount( - autumnHandler({ - identify: async (request) => { - // get the user from your auth provider (example: better-auth) - const session = await auth.api.getSession({ - headers: request.headers, - }); - - return { - customerId: session?.user.id, - customerData: { - name: session?.user.name, - email: session?.user.email, - }, - }; - }, - }), - ) - .listen(3002); -``` - -```typescript Express -import express from "express"; -import { autumnHandler } from "autumn-js/express"; - -const app = express(); - -// Body parser is required before the Autumn handler -app.use(express.json()); -app.use( - "/api/autumn", - autumnHandler({ - identify: async (req) => { - // get the user from your auth provider (example: better-auth) - const session = await auth.api.getSession({ - headers: req.headers, - }); - - return { - customerId: session?.user.id, - customerData: { - name: session?.user.name, - email: session?.user.email, - }, - }; - }, - }), -); -``` - -```typescript Elysia -import { autumnHandler } from "autumn-js/webStandard"; -import { Elysia } from "elysia"; - const app = new Elysia() .mount( autumnHandler({ diff --git a/apps/docs/mintlify/documentation/modelling-pricing/auto-top-ups.mdx b/apps/docs/mintlify/documentation/modelling-pricing/auto-top-ups.mdx index 0427a4dd5..d00314822 100644 --- a/apps/docs/mintlify/documentation/modelling-pricing/auto-top-ups.mdx +++ b/apps/docs/mintlify/documentation/modelling-pricing/auto-top-ups.mdx @@ -59,12 +59,16 @@ The one-off prepaid item (`$10 per 1,000 credits`) is what Autumn uses to replen -1. Navigate to the **Customers** page -2. Click on a customer -3. Under their balance for a feature, configure **Auto Top-Up**: - - **Threshold**: the balance level that triggers a top-up - - **Quantity**: how many units to purchase each time -4. The customer's plan must include a **one-off prepaid item** for the feature. This is what Autumn charges when a top-up is triggered. +1. Navigate to the **Plans** page and select (or create) the plan you want to add auto top-ups to +2. Add a new item for the feature with: + - **Interval** set to **One-Off** + - **Billing method** set to **Prepaid** + - Configure the price and billing units (e.g. $10 per 1,000 credits) +3. Configure auto top-ups per customer via the API (see below) + + +The same feature can appear as multiple items on a plan. For example, you might have a monthly allowance of 5,000 credits **and** a one-off prepaid item for top-ups — both referencing the same feature. + From 5368e3851cc475200f31ca821bb6f4b0c7eddeec Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Sat, 21 Mar 2026 22:40:39 +0000 Subject: [PATCH 19/49] fix: invalidate product list cache on plan create, save, and version create Made-with: Cursor --- vite/src/views/admin/adminUtils.ts | 13 +++++++++++-- vite/src/views/admin/components/ImpersonateBtn.tsx | 2 +- vite/src/views/command-bar/CommandBar.tsx | 4 ++-- .../products/plan/components/SaveChangesBar.tsx | 9 +++------ .../plan/versioning/ConfirmNewVersionDialog.tsx | 5 ++++- .../products/components/CreateProductSheet.tsx | 4 ++++ 6 files changed, 25 insertions(+), 12 deletions(-) diff --git a/vite/src/views/admin/adminUtils.ts b/vite/src/views/admin/adminUtils.ts index 063770139..102c54174 100644 --- a/vite/src/views/admin/adminUtils.ts +++ b/vite/src/views/admin/adminUtils.ts @@ -41,8 +41,13 @@ export const getCusProductHoverTexts = (cusProduct: FullCusProduct) => { ]; }; -export const impersonateUser = async (userId: string) => { - console.log("impersonating user", userId); +export const impersonateUser = async ({ + userId, + organizationId, +}: { + userId: string; + organizationId?: string; +}) => { try { await authClient.admin.stopImpersonating(); } catch (error) { @@ -57,6 +62,10 @@ export const impersonateUser = async (userId: string) => { return; } + if (organizationId) { + await authClient.organization.setActive({ organizationId }); + } + clearOrgCache(); window.location.reload(); }; diff --git a/vite/src/views/admin/components/ImpersonateBtn.tsx b/vite/src/views/admin/components/ImpersonateBtn.tsx index e5f4527f8..4f6d7f081 100644 --- a/vite/src/views/admin/components/ImpersonateBtn.tsx +++ b/vite/src/views/admin/components/ImpersonateBtn.tsx @@ -16,7 +16,7 @@ export const ImpersonateButton = ({ userId }: { userId?: string }) => { onClick={async () => { setLoading(true); try { - await impersonateUser(userId); + await impersonateUser({ userId }); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; diff --git a/vite/src/views/command-bar/CommandBar.tsx b/vite/src/views/command-bar/CommandBar.tsx index 0a70e4366..ae32eefc5 100644 --- a/vite/src/views/command-bar/CommandBar.tsx +++ b/vite/src/views/command-bar/CommandBar.tsx @@ -519,7 +519,7 @@ const CommandBar = () => { subtext={org.slug} onSelect={async () => { try { - await impersonateUser(firstUser.id); + await impersonateUser({ userId: firstUser.id, organizationId: org.id }); closeDialog(); } catch (error) { console.error("Failed to impersonate user:", error); @@ -548,7 +548,7 @@ const CommandBar = () => { onSelect={async () => { try { closeDialog(); - await impersonateUser(user.id); + await impersonateUser({ userId: user.id }); } catch (error) { console.error("Failed to impersonate user:", error); } diff --git a/vite/src/views/products/plan/components/SaveChangesBar.tsx b/vite/src/views/products/plan/components/SaveChangesBar.tsx index 4fb5ccfae..0a9f575a3 100644 --- a/vite/src/views/products/plan/components/SaveChangesBar.tsx +++ b/vite/src/views/products/plan/components/SaveChangesBar.tsx @@ -38,7 +38,7 @@ export const SaveChangesBar = ({ const [saving, setSaving] = useState(false); - const { refetch } = useProductsQuery(); + const { invalidate: invalidateProducts } = useProductsQuery(); const { counts, isLoading } = useProductCountsQuery(); const { refetch: queryRefetch } = useProductQuery(); @@ -92,11 +92,8 @@ export const SaveChangesBar = ({ productId: product.id, product, onSuccess: async () => { - if (isOnboarding) { - await refetch(); - } else { - await queryRefetch(); - } + await queryRefetch(); + invalidateProducts(); }, }); diff --git a/vite/src/views/products/plan/versioning/ConfirmNewVersionDialog.tsx b/vite/src/views/products/plan/versioning/ConfirmNewVersionDialog.tsx index 15097b9e5..51692dd16 100644 --- a/vite/src/views/products/plan/versioning/ConfirmNewVersionDialog.tsx +++ b/vite/src/views/products/plan/versioning/ConfirmNewVersionDialog.tsx @@ -11,6 +11,7 @@ import { DialogTrigger, } from "@/components/v2/dialogs/Dialog"; import { Input } from "@/components/v2/inputs/Input"; +import { useProductsQuery } from "@/hooks/queries/useProductsQuery"; import { useProductStore } from "@/hooks/stores/useProductStore"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { useProductQuery } from "../../product/hooks/useProductQuery"; @@ -28,6 +29,7 @@ export default function ConfirmNewVersionDialog({ const axiosInstance = useAxiosInstance(); const product = useProductStore((s) => s.product); const { refetch } = useProductQuery(); + const { invalidate: invalidateProducts } = useProductsQuery(); const [confirmText, setConfirmText] = useState(""); const [isLoading, setIsLoading] = useState(false); @@ -45,7 +47,8 @@ export default function ConfirmNewVersionDialog({ product, onSuccess: async () => { await refetch(); - onVersionCreated?.(); // Reset editing state + invalidateProducts(); + onVersionCreated?.(); }, }); setIsLoading(false); diff --git a/vite/src/views/products/products/components/CreateProductSheet.tsx b/vite/src/views/products/products/components/CreateProductSheet.tsx index e4fc728b5..7255b3eb9 100644 --- a/vite/src/views/products/products/components/CreateProductSheet.tsx +++ b/vite/src/views/products/products/components/CreateProductSheet.tsx @@ -9,6 +9,7 @@ import { SheetHeader, } from "@/components/v2/sheets/SharedSheetComponents"; import { Sheet, SheetContent } from "@/components/v2/sheets/Sheet"; +import { useProductsQuery } from "@/hooks/queries/useProductsQuery"; import { useProductStore } from "@/hooks/stores/useProductStore"; import { ProductService } from "@/services/products/ProductService"; import { useAxiosInstance } from "@/services/useAxiosInstance"; @@ -44,6 +45,7 @@ function CreateProductSheet({ const axiosInstance = useAxiosInstance(); const navigate = useNavigate(); + const { invalidate } = useProductsQuery(); const handleCreateClicked = async () => { const productName = product.name?.trim() || ""; @@ -60,6 +62,8 @@ function CreateProductSheet({ product, ); + invalidate(); + if (onSuccess) { await onSuccess(newProduct); } else { From 2f2520ac66f97c4c5b96dd59fc300525d792c377 Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Sat, 21 Mar 2026 22:42:16 +0000 Subject: [PATCH 20/49] feat: add env+org aware query keys to eliminate stale cache on switch Introduce useQueryKeyFactory hook that appends [env, orgId] to all React Query keys. Remove queryClient.clear() from env switch and window.location.reload() from org switch. Redirect to sandbox path when switching to a sandbox-only org. Made-with: Cursor --- vite/src/app/layout.tsx | 11 ++-- .../attach-product/use-attach-preview.ts | 4 +- .../forms/attach-v2/hooks/useAttachPreview.ts | 4 +- .../use-update-subscription-preview.ts | 4 +- vite/src/hooks/common/useQueryKeyFactory.ts | 12 ++++ .../hooks/queries/revcat/useRCMappings.tsx | 4 +- .../hooks/queries/revcat/useRCProducts.tsx | 4 +- .../queries/revcat/useRevenueCatQuery.tsx | 4 +- vite/src/hooks/queries/useDevQuery.tsx | 4 +- vite/src/hooks/queries/useFeaturesQuery.tsx | 4 +- vite/src/hooks/queries/useGeneralQuery.tsx | 4 +- vite/src/hooks/queries/useInvitesQuery.tsx | 4 +- vite/src/hooks/queries/useOrgStripeQuery.tsx | 5 +- .../hooks/queries/useProductVersionQuery.tsx | 4 +- vite/src/hooks/queries/useProductsQuery.tsx | 6 +- vite/src/hooks/queries/useRewardsQuery.tsx | 4 +- .../hooks/queries/useStripeCouponsQuery.tsx | 4 +- vite/src/hooks/queries/useVercelQuery.tsx | 4 +- vite/src/views/command-bar/CommandBar.tsx | 19 +++--- .../customer/hooks/useCusEventsQuery.tsx | 4 +- .../customers/customer/hooks/useCusQuery.tsx | 4 +- .../customer/hooks/useCusReferralQuery.tsx | 4 +- .../product/hooks/useCusProductQuery.tsx | 6 +- .../customers/hooks/useCusSearchQuery.tsx | 11 ++-- .../customers/hooks/useFullCusSearchQuery.tsx | 6 +- .../table/customer-list/CustomerListTable.tsx | 6 +- .../components/ShowCustomerObjectSheet.tsx | 4 +- .../hooks/useInvoiceLineItemsQuery.ts | 4 +- vite/src/views/main-sidebar/EnvDropdown.tsx | 7 --- .../main-sidebar/components/OrgDropdown.tsx | 59 ++++++++++++------- .../hooks/useOnboardingProgress.tsx | 8 ++- .../hooks/queries/useMigrationsQuery.tsx.tsx | 4 +- .../hooks/queries/useProductCountsQuery.tsx | 4 +- .../product/hooks/useProductQuery.tsx | 4 +- 34 files changed, 162 insertions(+), 82 deletions(-) create mode 100644 vite/src/hooks/common/useQueryKeyFactory.ts diff --git a/vite/src/app/layout.tsx b/vite/src/app/layout.tsx index 8d4689611..ccf7ce97c 100644 --- a/vite/src/app/layout.tsx +++ b/vite/src/app/layout.tsx @@ -52,16 +52,13 @@ export function MainLayout() { // Redirect to sandbox if not deployed useEffect(() => { - if (!orgLoading && org && !org.deployed) { + if (!orgLoading && org && !org.deployed && env !== AppEnv.Sandbox) { const pathname = window.location.pathname; - if (!pathname.startsWith("/sandbox")) { - const search = window.location.search; - navigate(`/sandbox${pathname}${search}`); - } + const search = window.location.search; + navigate(`/sandbox${pathname}${search}`); } - }, [org, orgLoading, navigate]); + }, [orgLoading, org, env, navigate]); - // Show loading screen while data is loading if (isPending || orgLoading) { return ( { if (!attachBody || !params.customerId) { return null; diff --git a/vite/src/components/forms/attach-v2/hooks/useAttachPreview.ts b/vite/src/components/forms/attach-v2/hooks/useAttachPreview.ts index 799f724b9..eb5ec76a3 100644 --- a/vite/src/components/forms/attach-v2/hooks/useAttachPreview.ts +++ b/vite/src/components/forms/attach-v2/hooks/useAttachPreview.ts @@ -2,6 +2,7 @@ import type { AttachParamsV0, AttachPreviewResponse } from "@autumn/shared"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; import type { AxiosError } from "axios"; import { useEffect, useMemo, useState } from "react"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; const ATTACH_PREVIEW_EXPAND = [ @@ -19,6 +20,7 @@ export function useAttachPreview({ enabled, }: UseAttachPreviewParams) { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const shouldEnable = enabled !== undefined ? enabled : !!requestBody; @@ -39,7 +41,7 @@ export function useAttachPreview({ const isDebouncing = queryKeyDeps !== debouncedQueryKey; const query = useQuery({ - queryKey: ["attach-preview-v2", debouncedQueryKey], + queryKey: buildKey(["attach-preview-v2", debouncedQueryKey]), queryFn: async () => { if (!requestBody) { return null; diff --git a/vite/src/components/forms/update-subscription/use-update-subscription-preview.ts b/vite/src/components/forms/update-subscription/use-update-subscription-preview.ts index e96d1b7ac..a0ac0b5aa 100644 --- a/vite/src/components/forms/update-subscription/use-update-subscription-preview.ts +++ b/vite/src/components/forms/update-subscription/use-update-subscription-preview.ts @@ -6,6 +6,7 @@ import { useQuery } from "@tanstack/react-query"; import type { AxiosError } from "axios"; import { useEffect, useState } from "react"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; const UPDATE_PREVIEW_EXPAND = [ @@ -22,6 +23,7 @@ export function useUpdateSubscriptionPreview({ enabled?: boolean; }) { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const shouldEnable = enabled !== undefined ? enabled : !!body; @@ -45,7 +47,7 @@ export function useUpdateSubscriptionPreview({ }, [queryKeyDeps, debouncedQueryKey]); const query = useQuery({ - queryKey: ["update-subscription-preview", debouncedQueryKey], + queryKey: buildKey(["update-subscription-preview", debouncedQueryKey]), queryFn: async () => { if (!body) return null; diff --git a/vite/src/hooks/common/useQueryKeyFactory.ts b/vite/src/hooks/common/useQueryKeyFactory.ts new file mode 100644 index 000000000..379711b25 --- /dev/null +++ b/vite/src/hooks/common/useQueryKeyFactory.ts @@ -0,0 +1,12 @@ +import { useEnv } from "@/utils/envUtils"; +import { useOrg } from "./useOrg"; + +/** + * Builds query keys scoped to the current env + org. + * Prevents cross-env and cross-org cache collisions. + */ +export const useQueryKeyFactory = () => { + const env = useEnv(); + const { org } = useOrg(); + return (key: readonly unknown[]) => [...key, env, org?.id]; +}; diff --git a/vite/src/hooks/queries/revcat/useRCMappings.tsx b/vite/src/hooks/queries/revcat/useRCMappings.tsx index 96143b37c..8fb586aac 100644 --- a/vite/src/hooks/queries/revcat/useRCMappings.tsx +++ b/vite/src/hooks/queries/revcat/useRCMappings.tsx @@ -1,5 +1,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; interface RCMapping { @@ -17,9 +18,10 @@ interface SaveMappingInput { export const useRCMappings = () => { const axiosInstance = useAxiosInstance(); const queryClient = useQueryClient(); + const buildKey = useQueryKeyFactory(); const { data: mappings = [], isLoading } = useQuery({ - queryKey: ["revenuecat-mappings"], + queryKey: buildKey(["revenuecat-mappings"]), queryFn: async () => { const { data } = await axiosInstance.get<{ mappings: RCMapping[] }>( "/v1/organization/revenuecat/mappings", diff --git a/vite/src/hooks/queries/revcat/useRCProducts.tsx b/vite/src/hooks/queries/revcat/useRCProducts.tsx index a42df2343..5c4161ea2 100644 --- a/vite/src/hooks/queries/revcat/useRCProducts.tsx +++ b/vite/src/hooks/queries/revcat/useRCProducts.tsx @@ -1,4 +1,5 @@ import { useQuery } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; interface RevenueCatProduct { @@ -12,6 +13,7 @@ interface RevenueCatProductsResponse { export const useRCProducts = () => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const fetcher = async () => { try { @@ -30,7 +32,7 @@ export const useRCProducts = () => { error, refetch, } = useQuery({ - queryKey: ["revenuecat-products"], + queryKey: buildKey(["revenuecat-products"]), queryFn: fetcher, }); diff --git a/vite/src/hooks/queries/revcat/useRevenueCatQuery.tsx b/vite/src/hooks/queries/revcat/useRevenueCatQuery.tsx index 94e5bf258..56eee55eb 100644 --- a/vite/src/hooks/queries/revcat/useRevenueCatQuery.tsx +++ b/vite/src/hooks/queries/revcat/useRevenueCatQuery.tsx @@ -1,4 +1,5 @@ import { useQuery } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; interface RevenueCatConfig { @@ -13,6 +14,7 @@ interface RevenueCatConfig { export const useRevenueCatQuery = () => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const fetcher = async () => { try { const { data }: { data: RevenueCatConfig } = await axiosInstance.get( @@ -24,7 +26,7 @@ export const useRevenueCatQuery = () => { } }; const { data, isLoading, error, refetch } = useQuery({ - queryKey: ["revenuecat"], + queryKey: buildKey(["revenuecat"]), queryFn: fetcher, }); diff --git a/vite/src/hooks/queries/useDevQuery.tsx b/vite/src/hooks/queries/useDevQuery.tsx index e0c642202..b76470ef1 100644 --- a/vite/src/hooks/queries/useDevQuery.tsx +++ b/vite/src/hooks/queries/useDevQuery.tsx @@ -1,8 +1,10 @@ import { useQuery } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; export const useDevQuery = () => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const fetcher = async () => { try { const { data } = await axiosInstance.get("/dev/data"); @@ -12,7 +14,7 @@ export const useDevQuery = () => { } }; const { data, isLoading, error, refetch } = useQuery({ - queryKey: ["dev"], + queryKey: buildKey(["dev"]), queryFn: fetcher, }); diff --git a/vite/src/hooks/queries/useFeaturesQuery.tsx b/vite/src/hooks/queries/useFeaturesQuery.tsx index 55694787c..5557adb07 100644 --- a/vite/src/hooks/queries/useFeaturesQuery.tsx +++ b/vite/src/hooks/queries/useFeaturesQuery.tsx @@ -1,9 +1,11 @@ import type { Feature } from "@autumn/shared"; import { useQuery } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; export const useFeaturesQuery = () => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const fetchFeatures = async () => { const { data } = await axiosInstance.get("/products/features"); @@ -13,7 +15,7 @@ export const useFeaturesQuery = () => { const { data, isLoading, error, refetch } = useQuery<{ features: Feature[]; }>({ - queryKey: ["features"], + queryKey: buildKey(["features"]), queryFn: fetchFeatures, }); diff --git a/vite/src/hooks/queries/useGeneralQuery.tsx b/vite/src/hooks/queries/useGeneralQuery.tsx index d1b2e6da7..9b8e46f5b 100644 --- a/vite/src/hooks/queries/useGeneralQuery.tsx +++ b/vite/src/hooks/queries/useGeneralQuery.tsx @@ -1,4 +1,5 @@ import { useQuery } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; export const useGeneralQuery = ({ @@ -13,6 +14,7 @@ export const useGeneralQuery = ({ enabled?: boolean; }) => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const fetcher = async () => { const { data } = await axiosInstance.request({ @@ -24,7 +26,7 @@ export const useGeneralQuery = ({ }; const { data, isLoading, error, refetch } = useQuery({ - queryKey: queryKey || ["general", url], + queryKey: buildKey(queryKey || ["general", url]), queryFn: fetcher, enabled, }); diff --git a/vite/src/hooks/queries/useInvitesQuery.tsx b/vite/src/hooks/queries/useInvitesQuery.tsx index 47463057d..50e7ef342 100644 --- a/vite/src/hooks/queries/useInvitesQuery.tsx +++ b/vite/src/hooks/queries/useInvitesQuery.tsx @@ -1,9 +1,11 @@ import { type FullInvite, Invite, User } from "@autumn/shared"; import { useQuery } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; export const useInvitesQuery = () => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const fetcher = async () => { const { data } = await axiosInstance.get("/organization/invites"); return data; @@ -11,7 +13,7 @@ export const useInvitesQuery = () => { const { data, isLoading, error, refetch } = useQuery<{ invites: FullInvite[]; }>({ - queryKey: ["invitations"], + queryKey: buildKey(["invitations"]), queryFn: fetcher, }); diff --git a/vite/src/hooks/queries/useOrgStripeQuery.tsx b/vite/src/hooks/queries/useOrgStripeQuery.tsx index a71cea7d3..d0ed74821 100644 --- a/vite/src/hooks/queries/useOrgStripeQuery.tsx +++ b/vite/src/hooks/queries/useOrgStripeQuery.tsx @@ -1,12 +1,13 @@ import { useQuery } from "@tanstack/react-query"; import type Stripe from "stripe"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { useOrg } from "../common/useOrg"; /** Fetches organization Stripe account information */ export const useOrgStripeQuery = () => { const axiosInstance = useAxiosInstance(); - // const orgId = authClient.getSession()?.session.activeOrganizationId; + const buildKey = useQueryKeyFactory(); const { org } = useOrg(); const fetchStripeAccount = async () => { @@ -17,7 +18,7 @@ export const useOrgStripeQuery = () => { }; const { data, isLoading, error, refetch } = useQuery({ - queryKey: ["org", org?.id, "stripe"], + queryKey: buildKey(["org", org?.id, "stripe"]), queryFn: fetchStripeAccount, retry: false, enabled: !!org?.id, diff --git a/vite/src/hooks/queries/useProductVersionQuery.tsx b/vite/src/hooks/queries/useProductVersionQuery.tsx index 35b8ac302..09499bdda 100644 --- a/vite/src/hooks/queries/useProductVersionQuery.tsx +++ b/vite/src/hooks/queries/useProductVersionQuery.tsx @@ -1,5 +1,6 @@ import type { ProductV2 } from "@autumn/shared"; import { useQuery } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; /** Fetches product data for a specific version (or latest if version is omitted). */ @@ -13,8 +14,9 @@ export function useProductVersionQuery({ enabled?: boolean; }) { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); return useQuery({ - queryKey: ["product-version", productId, version], + queryKey: buildKey(["product-version", productId, version]), queryFn: async () => { const { data } = await axiosInstance.get(`/products/${productId}/data`, { params: version ? { version } : undefined, diff --git a/vite/src/hooks/queries/useProductsQuery.tsx b/vite/src/hooks/queries/useProductsQuery.tsx index c6fdba4e7..334b1674f 100644 --- a/vite/src/hooks/queries/useProductsQuery.tsx +++ b/vite/src/hooks/queries/useProductsQuery.tsx @@ -1,5 +1,6 @@ import type { FullProduct, ProductCounts, ProductV2 } from "@autumn/shared"; import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; // Stable empty object reference to prevent infinite re-renders @@ -11,6 +12,7 @@ const EMPTY_COUNTS: Record = {}; export const useProductsQuery = () => { const axiosInstance = useAxiosInstance(); const queryClient = useQueryClient(); + const buildKey = useQueryKeyFactory(); const fetchProducts = async () => { const { data } = await axiosInstance.get("/products/products"); @@ -26,7 +28,7 @@ export const useProductsQuery = () => { products: ProductV2[]; groupToDefaults: Record>; }>({ - queryKey: ["products"], + queryKey: buildKey(["products"]), queryFn: fetchProducts, }); @@ -35,7 +37,7 @@ export const useProductsQuery = () => { isLoading: isCountsLoading, refetch: countsRefetch, } = useQuery>({ - queryKey: ["product_counts"], + queryKey: buildKey(["product_counts"]), queryFn: fetchProductCounts, }); diff --git a/vite/src/hooks/queries/useRewardsQuery.tsx b/vite/src/hooks/queries/useRewardsQuery.tsx index 38281484b..7eb30e5a0 100644 --- a/vite/src/hooks/queries/useRewardsQuery.tsx +++ b/vite/src/hooks/queries/useRewardsQuery.tsx @@ -1,9 +1,11 @@ import type { Reward, RewardProgram } from "@autumn/shared"; import { useQuery } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; export const useRewardsQuery = () => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const fetchRewards = async () => { const { data } = await axiosInstance.get("/products/rewards"); @@ -11,7 +13,7 @@ export const useRewardsQuery = () => { }; const { data, isLoading, error, refetch } = useQuery({ - queryKey: ["rewards"], + queryKey: buildKey(["rewards"]), queryFn: fetchRewards, }); diff --git a/vite/src/hooks/queries/useStripeCouponsQuery.tsx b/vite/src/hooks/queries/useStripeCouponsQuery.tsx index 606dfe492..38c03a8ee 100644 --- a/vite/src/hooks/queries/useStripeCouponsQuery.tsx +++ b/vite/src/hooks/queries/useStripeCouponsQuery.tsx @@ -1,12 +1,14 @@ import { useQuery } from "@tanstack/react-query"; import type Stripe from "stripe"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; export const useStripeCouponsQuery = () => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const { data, isLoading, error, refetch } = useQuery({ - queryKey: ["stripe_coupons"], + queryKey: buildKey(["stripe_coupons"]), queryFn: () => axiosInstance.get("/products/stripe_coupons").then((r) => r.data), }); diff --git a/vite/src/hooks/queries/useVercelQuery.tsx b/vite/src/hooks/queries/useVercelQuery.tsx index e33995676..423a1614e 100644 --- a/vite/src/hooks/queries/useVercelQuery.tsx +++ b/vite/src/hooks/queries/useVercelQuery.tsx @@ -1,8 +1,10 @@ import { useQuery } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; export const useVercelQuery = () => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const fetcher = async () => { try { const { data }: { data: { url: string } } = await axiosInstance.get( @@ -14,7 +16,7 @@ export const useVercelQuery = () => { } }; const { data, isLoading, error, refetch } = useQuery({ - queryKey: ["vercel_sink"], + queryKey: buildKey(["vercel_sink"]), queryFn: fetcher, }); diff --git a/vite/src/views/command-bar/CommandBar.tsx b/vite/src/views/command-bar/CommandBar.tsx index ae32eefc5..6fd480e11 100644 --- a/vite/src/views/command-bar/CommandBar.tsx +++ b/vite/src/views/command-bar/CommandBar.tsx @@ -6,7 +6,6 @@ import { GearIcon, } from "@phosphor-icons/react"; import { useQueries, useQuery } from "@tanstack/react-query"; - import { CircleUserRoundIcon, Monitor, @@ -27,6 +26,7 @@ import { import { Skeleton } from "@/components/ui/skeleton"; import { useTheme } from "@/contexts/ThemeProvider"; import { useOrg } from "@/hooks/common/useOrg"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useProductsQuery } from "@/hooks/queries/useProductsQuery"; import { useCommandBarStore } from "@/hooks/stores/useCommandBarStore"; import { useListOrganizations } from "@/lib/auth-client"; @@ -38,7 +38,7 @@ import { useAdmin } from "@/views/admin/hooks/useAdmin"; import { CommandRow } from "@/views/command-bar/command-row"; import { calculateRelevanceScore } from "@/views/command-bar/commandUtils"; import { useCommandBarHotkeys } from "@/views/command-bar/useCommandBarHotkeys"; -import { handleSwitchOrg } from "@/views/main-sidebar/components/OrgDropdown"; +import { useOrgSwitch } from "@/views/main-sidebar/components/OrgDropdown"; import { useEnvChange } from "@/views/main-sidebar/EnvDropdown"; type User = { @@ -74,6 +74,8 @@ const CommandBar = () => { const navigate = useNavigate(); const env = useEnv(); const handleEnvChange = useEnvChange(); + const switchOrg = useOrgSwitch(); + const buildKey = useQueryKeyFactory(); const { data: orgs, isPending: isLoadingOrgs } = useListOrganizations(); const axiosInstance = useAxiosInstance(); const { isAdmin } = useAdmin(); @@ -150,7 +152,7 @@ const CommandBar = () => { useQuery<{ customers: Customer[]; }>({ - queryKey: ["command-palette-customers-search", debouncedSearch], + queryKey: buildKey(["command-palette-customers-search", debouncedSearch]), queryFn: async () => { // Always use the search term in the backend query const { data } = await axiosInstance.post(`/customers/all/search`, { @@ -174,7 +176,7 @@ const CommandBar = () => { const [orgsQuery, usersQuery] = useQueries({ queries: [ { - queryKey: ["command-palette-orgs-search", debouncedSearch], + queryKey: buildKey(["command-palette-orgs-search", debouncedSearch]), queryFn: async () => { const params = new URLSearchParams(); if (debouncedSearch) params.append("search", debouncedSearch); @@ -186,7 +188,7 @@ const CommandBar = () => { enabled: impersonateEnabled, }, { - queryKey: ["command-palette-users-search", debouncedSearch], + queryKey: buildKey(["command-palette-users-search", debouncedSearch]), queryFn: async () => { const params = new URLSearchParams(); if (debouncedSearch) params.append("search", debouncedSearch); @@ -519,7 +521,10 @@ const CommandBar = () => { subtext={org.slug} onSelect={async () => { try { - await impersonateUser({ userId: firstUser.id, organizationId: org.id }); + await impersonateUser({ + userId: firstUser.id, + organizationId: org.id, + }); closeDialog(); } catch (error) { console.error("Failed to impersonate user:", error); @@ -604,7 +609,7 @@ const CommandBar = () => { title={org.name} subtext={org.slug} onSelect={() => { - handleSwitchOrg(org.id); + switchOrg({ orgId: org.id }); }} /> ))} diff --git a/vite/src/views/customers/customer/hooks/useCusEventsQuery.tsx b/vite/src/views/customers/customer/hooks/useCusEventsQuery.tsx index 65dedbd9e..60fc939dd 100644 --- a/vite/src/views/customers/customer/hooks/useCusEventsQuery.tsx +++ b/vite/src/views/customers/customer/hooks/useCusEventsQuery.tsx @@ -1,5 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { useParams } from "react-router"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; type IntervalType = "7d" | "30d" | "90d"; @@ -15,6 +16,7 @@ export const useCusEventsQuery = ({ customerId?: string; } = {}) => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const { customer_id } = useParams(); const id = customerId ?? customer_id; @@ -32,7 +34,7 @@ export const useCusEventsQuery = ({ }; const { data, isLoading, isFetching, error } = useQuery({ - queryKey: ["customer_events", id, interval, limit], + queryKey: buildKey(["customer_events", id, interval, limit]), queryFn: fetcher, enabled: !!id, }); diff --git a/vite/src/views/customers/customer/hooks/useCusQuery.tsx b/vite/src/views/customers/customer/hooks/useCusQuery.tsx index daff19ec8..fe6f5877a 100644 --- a/vite/src/views/customers/customer/hooks/useCusQuery.tsx +++ b/vite/src/views/customers/customer/hooks/useCusQuery.tsx @@ -1,6 +1,7 @@ import { useQuery } from "@tanstack/react-query"; import { useMemo } from "react"; import { useParams } from "react-router"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; import { useProductsQuery } from "@/hooks/queries/useProductsQuery"; import { useAxiosInstance } from "@/services/useAxiosInstance"; @@ -10,6 +11,7 @@ import { useCachedCustomer } from "./useCachedCustomer"; export const useCusQuery = ({ enabled = true }: { enabled?: boolean } = {}) => { const { customer_id } = useParams(); const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const { getCachedCustomer } = useCachedCustomer(customer_id); const cachedCustomer = useMemo(getCachedCustomer, [getCachedCustomer]); @@ -29,7 +31,7 @@ export const useCusQuery = ({ enabled = true }: { enabled?: boolean } = {}) => { error, refetch, } = useQuery({ - queryKey: ["customer", customer_id], + queryKey: buildKey(["customer", customer_id]), queryFn: fetcher, enabled: enabled && !!customer_id, retry: false, diff --git a/vite/src/views/customers/customer/hooks/useCusReferralQuery.tsx b/vite/src/views/customers/customer/hooks/useCusReferralQuery.tsx index b6cf0dd1a..8c486d05b 100644 --- a/vite/src/views/customers/customer/hooks/useCusReferralQuery.tsx +++ b/vite/src/views/customers/customer/hooks/useCusReferralQuery.tsx @@ -1,10 +1,12 @@ import { useQuery } from "@tanstack/react-query"; import { useParams } from "react-router"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; export const useCusReferralQuery = () => { const { customer_id } = useParams(); const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const referralFetcher = async () => { const { data } = await axiosInstance.get( @@ -20,7 +22,7 @@ export const useCusReferralQuery = () => { error: cusRewardError, refetch: cusRewardRefetch, } = useQuery({ - queryKey: ["customer_referrals", customer_id], + queryKey: buildKey(["customer_referrals", customer_id]), queryFn: referralFetcher, retry: false, }); diff --git a/vite/src/views/customers/customer/product/hooks/useCusProductQuery.tsx b/vite/src/views/customers/customer/product/hooks/useCusProductQuery.tsx index 2a3aaaea6..433a9c927 100644 --- a/vite/src/views/customers/customer/product/hooks/useCusProductQuery.tsx +++ b/vite/src/views/customers/customer/product/hooks/useCusProductQuery.tsx @@ -4,11 +4,13 @@ import { debounce } from "lodash"; import { parseAsInteger, parseAsString, useQueryStates } from "nuqs"; import { useEffect, useMemo, useState } from "react"; import { useParams } from "react-router"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { useCusProductCache } from "./useCusProductCache"; export const useCusProductQuery = () => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const { customer_id, product_id } = useParams(); const [queryStates] = useQueryStates({ version: parseAsInteger, @@ -52,14 +54,14 @@ export const useCusProductQuery = () => { cusProduct: FullCusProduct; product: ProductV2; }>({ - queryKey: [ + queryKey: buildKey([ "customer_product", customer_id, product_id, stableStates.version, stableStates.id, stableStates.entity_id, - ], + ]), queryFn: fetcher, }); diff --git a/vite/src/views/customers/hooks/useCusSearchQuery.tsx b/vite/src/views/customers/hooks/useCusSearchQuery.tsx index bdcb7bbe3..21b6d3b0f 100644 --- a/vite/src/views/customers/hooks/useCusSearchQuery.tsx +++ b/vite/src/views/customers/hooks/useCusSearchQuery.tsx @@ -1,6 +1,7 @@ import type { CustomerWithProducts } from "@autumn/shared"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { useCustomersQueryStates } from "./useCustomersQueryStates"; @@ -9,6 +10,7 @@ export const useCusSearchQuery = () => { const trimmedSearch = queryStates.q.trim(); const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const fetcher = async () => { const { data } = await axiosInstance.post(`/customers/all/search`, { search: trimmedSearch, @@ -36,7 +38,7 @@ export const useCusSearchQuery = () => { customers: CustomerWithProducts[]; totalCount: number; }>({ - queryKey: [ + queryKey: buildKey([ "customers", queryStates.page, queryStates.pageSize, @@ -44,7 +46,7 @@ export const useCusSearchQuery = () => { queryStates.version, queryStates.none, trimmedSearch, - ], + ]), queryFn: fetcher, placeholderData: keepPreviousData, }); @@ -81,6 +83,7 @@ export const useCusSearchQueryV2 = ({ }) => { const trimmedSearch = search.trim(); const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const fetcher = async () => { const { data } = await axiosInstance.post(`/customers/all/search`, { search: trimmedSearch, @@ -108,7 +111,7 @@ export const useCusSearchQueryV2 = ({ customers: CustomerWithProducts[]; totalCount: number; }>({ - queryKey: [ + queryKey: buildKey([ "customers", page, page_size, @@ -116,7 +119,7 @@ export const useCusSearchQueryV2 = ({ filters?.version, filters?.none, trimmedSearch, - ], + ]), queryFn: fetcher, placeholderData: keepPreviousData, }); diff --git a/vite/src/views/customers/hooks/useFullCusSearchQuery.tsx b/vite/src/views/customers/hooks/useFullCusSearchQuery.tsx index 2205d9659..ede23a2ae 100644 --- a/vite/src/views/customers/hooks/useFullCusSearchQuery.tsx +++ b/vite/src/views/customers/hooks/useFullCusSearchQuery.tsx @@ -1,5 +1,6 @@ import type { FullCustomer } from "@autumn/shared"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { useCustomersQueryStates } from "./useCustomersQueryStates"; @@ -8,11 +9,12 @@ export const FULL_CUSTOMERS_QUERY_KEY = "full_customers"; export const useFullCusSearchQuery = () => { const { queryStates } = useCustomersQueryStates(); const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); return useQuery<{ fullCustomers: FullCustomer[]; }>({ - queryKey: [ + queryKey: buildKey([ FULL_CUSTOMERS_QUERY_KEY, queryStates.page, queryStates.pageSize, @@ -20,7 +22,7 @@ export const useFullCusSearchQuery = () => { queryStates.version, queryStates.none, queryStates.q, - ], + ]), queryFn: async ({ signal }) => { const { data } = await axiosInstance.post( `/customers/all/full_customers`, diff --git a/vite/src/views/customers2/components/table/customer-list/CustomerListTable.tsx b/vite/src/views/customers2/components/table/customer-list/CustomerListTable.tsx index 150a46ba8..7c42adb65 100644 --- a/vite/src/views/customers2/components/table/customer-list/CustomerListTable.tsx +++ b/vite/src/views/customers2/components/table/customer-list/CustomerListTable.tsx @@ -6,6 +6,7 @@ import { Table } from "@/components/general/table"; import { IconButton } from "@/components/v2/buttons/IconButton"; import { EmptyState } from "@/components/v2/empty-states/EmptyState"; import { useOrg } from "@/hooks/common/useOrg"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; import { useColumnVisibility } from "@/hooks/useColumnVisibility"; import { useEnv } from "@/utils/envUtils"; @@ -39,6 +40,7 @@ export function CustomerListTable({ const { features } = useFeaturesQuery(); const { queryStates } = useCustomersQueryStates(); + const buildKey = useQueryKeyFactory(); // Subscribe to full_customers query to get reactive updates // Query key must match useFullCusSearchQuery for proper cache sharing @@ -47,7 +49,7 @@ export function CustomerListTable({ isLoading: isFullCustomersLoading, isFetching: isFullCustomersFetching, } = useQuery<{ fullCustomers: FullCustomer[] }>({ - queryKey: [ + queryKey: buildKey([ FULL_CUSTOMERS_QUERY_KEY, queryStates.page, queryStates.pageSize, @@ -55,7 +57,7 @@ export function CustomerListTable({ queryStates.version, queryStates.none, queryStates.q, - ], + ]), // Placeholder queryFn - actual fetching is done by useFullCusSearchQuery queryFn: () => Promise.resolve({ fullCustomers: [] }), // Don't fetch - just subscribe to existing data from useFullCusSearchQuery diff --git a/vite/src/views/customers2/customer/components/ShowCustomerObjectSheet.tsx b/vite/src/views/customers2/customer/components/ShowCustomerObjectSheet.tsx index 11ac7b35b..0e5e5bab8 100644 --- a/vite/src/views/customers2/customer/components/ShowCustomerObjectSheet.tsx +++ b/vite/src/views/customers2/customer/components/ShowCustomerObjectSheet.tsx @@ -14,6 +14,7 @@ import { SheetHeader, SheetTitle, } from "@/components/v2/sheets/Sheet"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { getBackendErr } from "@/utils/genUtils"; @@ -37,9 +38,10 @@ export function ShowCustomerObjectSheet({ }: ShowCustomerObjectSheetProps) { const { customer_id } = useParams(); const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const { data, isLoading, error } = useQuery({ - queryKey: ["customer-object", customer_id, "expanded"], + queryKey: buildKey(["customer-object", customer_id, "expanded"]), queryFn: async () => { const { data } = await axiosInstance.get( `/v1/customers/${customer_id}?expand=${EXPAND_PARAMS}`, diff --git a/vite/src/views/customers2/hooks/useInvoiceLineItemsQuery.ts b/vite/src/views/customers2/hooks/useInvoiceLineItemsQuery.ts index d765ddfa8..543c65ba0 100644 --- a/vite/src/views/customers2/hooks/useInvoiceLineItemsQuery.ts +++ b/vite/src/views/customers2/hooks/useInvoiceLineItemsQuery.ts @@ -1,6 +1,7 @@ import type { InvoiceLineItem } from "@autumn/shared"; import { useQuery } from "@tanstack/react-query"; import { useMemo } from "react"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; interface UseInvoiceLineItemsQueryParams { @@ -17,6 +18,7 @@ export const useInvoiceLineItemsQuery = ({ enabled = true, }: UseInvoiceLineItemsQueryParams) => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const fetcher = async (): Promise => { if (invoiceIds.length === 0) { @@ -31,7 +33,7 @@ export const useInvoiceLineItemsQuery = ({ }; const { data, isLoading, error, refetch } = useQuery({ - queryKey: ["invoice-line-items", invoiceIds], + queryKey: buildKey(["invoice-line-items", invoiceIds]), queryFn: fetcher, enabled: enabled && invoiceIds.length > 0, staleTime: 5 * 60 * 1000, // 5 minutes diff --git a/vite/src/views/main-sidebar/EnvDropdown.tsx b/vite/src/views/main-sidebar/EnvDropdown.tsx index 90c11c2cd..b99702775 100644 --- a/vite/src/views/main-sidebar/EnvDropdown.tsx +++ b/vite/src/views/main-sidebar/EnvDropdown.tsx @@ -2,7 +2,6 @@ "use client"; import { AppEnv } from "@autumn/shared"; -import { useQueryClient } from "@tanstack/react-query"; import { Check } from "lucide-react"; import { useState } from "react"; import { useNavigate } from "react-router"; @@ -12,20 +11,14 @@ import { DropdownMenuContent, DropdownMenuItem, } from "@/components/ui/dropdown-menu"; -import { clearOrgCache } from "@/hooks/common/useOrg"; import { cn } from "@/lib/utils"; import { envToPath } from "@/utils/genUtils"; import { ExpandedEnvTrigger } from "./env-dropdown/ExpandedEnvTrigger"; export const useEnvChange = () => { const navigate = useNavigate(); - const queryClient = useQueryClient(); const handleEnvChange = (targetEnv: AppEnv, reset?: boolean) => { - // Clear all cached query data so it refetches for the new env - queryClient.clear(); - clearOrgCache(); - // Calculate the new path const newPath = envToPath(targetEnv, location.pathname); diff --git a/vite/src/views/main-sidebar/components/OrgDropdown.tsx b/vite/src/views/main-sidebar/components/OrgDropdown.tsx index c5f3ed536..3a2dbec8a 100644 --- a/vite/src/views/main-sidebar/components/OrgDropdown.tsx +++ b/vite/src/views/main-sidebar/components/OrgDropdown.tsx @@ -1,4 +1,6 @@ +import type { FrontendOrg } from "@autumn/shared"; import { DropdownMenuGroup } from "@radix-ui/react-dropdown-menu"; +import { useQueryClient } from "@tanstack/react-query"; import { ChevronDown, Monitor, @@ -9,7 +11,7 @@ import { Sun, } from "lucide-react"; import { useState } from "react"; -import { useSearchParams } from "react-router"; +import { useNavigate } from "react-router"; import { toast } from "sonner"; import { AdminHover } from "@/components/general/AdminHover"; import { Button } from "@/components/ui/button"; @@ -232,40 +234,53 @@ export const OrgDropdown = () => { ); }; -export const handleSwitchOrg = async ( - orgId: string, - setLoading?: (loading: boolean) => void, - setSearchParams?: (searchParams: URLSearchParams) => void, -) => { - setLoading?.(true); +/** Switches the active org via client-side state update (no page reload). */ +export const useOrgSwitch = () => { + const queryClient = useQueryClient(); + const navigate = useNavigate(); - try { - setSearchParams?.(new URLSearchParams()); - window.history.replaceState(null, "", window.location.pathname); + return async ({ + orgId, + setLoading, + }: { + orgId: string; + setLoading?: (loading: boolean) => void; + }) => { + setLoading?.(true); + try { + await authClient.organization.setActive({ + organizationId: orgId, + }); - await authClient.organization.setActive({ - organizationId: orgId, - }); + clearOrgCache(); + await queryClient.invalidateQueries({ queryKey: ["org"] }); - clearOrgCache(); - window.location.reload(); - } catch (error: any) { - toast.error(error.message); - } finally { - setLoading?.(false); - } + const newOrg = queryClient.getQueryData(["org"]); + if (newOrg && !newOrg.deployed) { + const pathname = window.location.pathname; + const search = window.location.search; + if (!pathname.startsWith("/sandbox")) { + navigate(`/sandbox${pathname}${search}`); + } + } + } catch (error: any) { + toast.error(error.message); + } finally { + setLoading?.(false); + } + }; }; const SwitchOrgItem = ({ org, setDropdownOpen }: any) => { const [loading, setLoading] = useState(false); - const [_, setSearchParams] = useSearchParams(); + const switchOrg = useOrgSwitch(); return ( { e.preventDefault(); - await handleSwitchOrg(org.id, setLoading, setSearchParams); + await switchOrg({ orgId: org.id, setLoading }); setDropdownOpen(false); }} shimmer={loading} diff --git a/vite/src/views/onboarding4/hooks/useOnboardingProgress.tsx b/vite/src/views/onboarding4/hooks/useOnboardingProgress.tsx index c9a9a3377..d58b765f8 100644 --- a/vite/src/views/onboarding4/hooks/useOnboardingProgress.tsx +++ b/vite/src/views/onboarding4/hooks/useOnboardingProgress.tsx @@ -2,6 +2,7 @@ import type { FullCustomer, ProductV2 } from "@autumn/shared"; import { useQuery } from "@tanstack/react-query"; import { useMemo } from "react"; import { create } from "zustand"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; const REFETCH_INTERVAL = 5000; @@ -54,13 +55,14 @@ interface OnboardingProgress { */ export const useOnboardingProgress = (): OnboardingProgress => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const { isDismissed, dismiss, show } = useOnboardingDismissedStore(); // Products query const { data: productsData, isLoading: productsLoading } = useQuery<{ products: ProductV2[]; }>({ - queryKey: ["onboarding-products"], + queryKey: buildKey(["onboarding-products"]), queryFn: async () => { const { data } = await axiosInstance.get("/products/products"); return data; @@ -80,7 +82,7 @@ export const useOnboardingProgress = (): OnboardingProgress => { const { data: customersData, isLoading: customersLoading } = useQuery<{ fullCustomers: FullCustomer[]; }>({ - queryKey: ["onboarding-customers"], + queryKey: buildKey(["onboarding-customers"]), queryFn: async () => { const { data } = await axiosInstance.post( "/customers/all/full_customers", @@ -98,7 +100,7 @@ export const useOnboardingProgress = (): OnboardingProgress => { const { data: eventsData, isLoading: eventsLoading } = useQuery<{ rawEvents: { data: unknown[] }; }>({ - queryKey: ["onboarding-events"], + queryKey: buildKey(["onboarding-events"]), queryFn: async () => { const { data } = await axiosInstance.post("/query/raw", { customer_id: null, diff --git a/vite/src/views/products/product/hooks/queries/useMigrationsQuery.tsx.tsx b/vite/src/views/products/product/hooks/queries/useMigrationsQuery.tsx.tsx index 9afcd5adb..ed64f9632 100644 --- a/vite/src/views/products/product/hooks/queries/useMigrationsQuery.tsx.tsx +++ b/vite/src/views/products/product/hooks/queries/useMigrationsQuery.tsx.tsx @@ -1,8 +1,10 @@ import { useQuery } from "@tanstack/react-query"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; export const useMigrationsQuery = () => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const fetchProductMigrations = async () => { const { data } = await axiosInstance.get("/products/migrations"); @@ -10,7 +12,7 @@ export const useMigrationsQuery = () => { }; const { data, isLoading, error, refetch } = useQuery({ - queryKey: ["migrations"], + queryKey: buildKey(["migrations"]), queryFn: fetchProductMigrations, retry: false, // Don't retry on error }); diff --git a/vite/src/views/products/product/hooks/queries/useProductCountsQuery.tsx b/vite/src/views/products/product/hooks/queries/useProductCountsQuery.tsx index 809108025..6f80df48d 100644 --- a/vite/src/views/products/product/hooks/queries/useProductCountsQuery.tsx +++ b/vite/src/views/products/product/hooks/queries/useProductCountsQuery.tsx @@ -1,5 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { useParams } from "react-router-dom"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useProductStore } from "@/hooks/stores/useProductStore"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { useProductQueryState } from "../useProductQuery"; @@ -10,6 +11,7 @@ export const useProductCountsQuery = ({ version?: number; } = {}) => { const axiosInstance = useAxiosInstance(); + const buildKey = useQueryKeyFactory(); const { product_id } = useParams(); const { queryStates } = useProductQueryState(); const product = useProductStore((s) => s.product); @@ -28,7 +30,7 @@ export const useProductCountsQuery = ({ }; const { data, isLoading, error, refetch } = useQuery({ - queryKey: ["product_counts", productId, version], + queryKey: buildKey(["product_counts", productId, version]), queryFn: fetchProductCounts, retry: false, // Don't retry on error (e.g., product not found) enabled: !!productId, // Only run query if productId exists diff --git a/vite/src/views/products/product/hooks/useProductQuery.tsx b/vite/src/views/products/product/hooks/useProductQuery.tsx index ec23cf600..b33d918b7 100644 --- a/vite/src/views/products/product/hooks/useProductQuery.tsx +++ b/vite/src/views/products/product/hooks/useProductQuery.tsx @@ -3,6 +3,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { parseAsInteger, parseAsString, useQueryStates } from "nuqs"; import { useMemo } from "react"; import { useParams } from "react-router"; +import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory"; import { useAxiosInstance } from "@/services/useAxiosInstance"; import { throwBackendError } from "@/utils/genUtils"; @@ -33,6 +34,7 @@ export const useProductQuery = () => { const axiosInstance = useAxiosInstance(); const queryClient = useQueryClient(); + const buildKey = useQueryKeyFactory(); const { getCachedProduct } = useCachedProduct({ productId: productId }); const cachedProduct = useMemo(getCachedProduct, []); @@ -62,7 +64,7 @@ export const useProductQuery = () => { }; const { data, isLoading, refetch, error } = useQuery({ - queryKey: ["product", productId, queryStates.version], + queryKey: buildKey(["product", productId, queryStates.version]), queryFn: fetcher, retry: false, // Don't retry on error (e.g., product not found) enabled: !!productId, // Only run query if productId exists From 80f57b98c2e979b8010d621a761b8e10fda2a1c1 Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Sun, 22 Mar 2026 02:15:34 +0000 Subject: [PATCH 21/49] feat: add static env pill for non-deployed orgs and fix env dropdown visibility Move deployed-org check into EnvDropdown so non-deployed orgs see a non-interactive StaticEnvPill instead of hiding the env indicator entirely. Also adjust dark-mode shimmer hover opacity. Made-with: Cursor --- vite/src/index.css | 11 ++++- vite/src/views/main-sidebar/EnvDropdown.tsx | 14 +++++- vite/src/views/main-sidebar/MainSidebar.tsx | 6 +-- .../env-dropdown/StaticEnvPill.tsx | 44 +++++++++++++++++++ 4 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 vite/src/views/main-sidebar/env-dropdown/StaticEnvPill.tsx diff --git a/vite/src/index.css b/vite/src/index.css index 6913e44ba..a32005938 100644 --- a/vite/src/index.css +++ b/vite/src/index.css @@ -529,7 +529,7 @@ input[type="number"]::-webkit-inner-spin-button { background: linear-gradient( 90deg, transparent, - rgba(255, 255, 255, 0.4), + rgba(255, 255, 255, 0.7), transparent ); pointer-events: none; @@ -542,6 +542,15 @@ input[type="number"]::-webkit-inner-spin-button { opacity: 1; } +.dark .shimmer-hover::after { + background: linear-gradient( + 90deg, + transparent, + rgba(25, 25, 25, 0.5), + transparent + ); +} + @keyframes shimmer-once { 0% { left: -150%; diff --git a/vite/src/views/main-sidebar/EnvDropdown.tsx b/vite/src/views/main-sidebar/EnvDropdown.tsx index b99702775..2356b7f97 100644 --- a/vite/src/views/main-sidebar/EnvDropdown.tsx +++ b/vite/src/views/main-sidebar/EnvDropdown.tsx @@ -11,15 +11,16 @@ import { DropdownMenuContent, DropdownMenuItem, } from "@/components/ui/dropdown-menu"; +import { useOrg } from "@/hooks/common/useOrg"; import { cn } from "@/lib/utils"; import { envToPath } from "@/utils/genUtils"; import { ExpandedEnvTrigger } from "./env-dropdown/ExpandedEnvTrigger"; +import { StaticEnvPill } from "./env-dropdown/StaticEnvPill"; export const useEnvChange = () => { const navigate = useNavigate(); const handleEnvChange = (targetEnv: AppEnv, reset?: boolean) => { - // Calculate the new path const newPath = envToPath(targetEnv, location.pathname); if (newPath && !reset) { @@ -38,10 +39,21 @@ export const useEnvChange = () => { }; export const EnvDropdown = ({ env }: { env: AppEnv }) => { + const { org, isLoading } = useOrg(); + const canSwitch = !isLoading && !!org?.deployed; + const [isHovered, setIsHovered] = useState(false); const [open, setOpen] = useState(false); const handleEnvChange = useEnvChange(); + if (!canSwitch) { + return ( +
+ +
+ ); + } + return (
void; } = {}) => { const env = useEnv(); - const { org } = useOrg(); const flags = useAutumnFlags(); @@ -145,8 +143,8 @@ export const MainSidebar = ({ )} - - {org?.deployed && } + +
{ + const env = useEnv(); + const { expanded } = useSidebarContext(); + + const isSandbox = env === AppEnv.Sandbox; + + return ( +
+
+
+
+ {isSandbox ? ( + + ) : ( + + )} +
+

+ {isSandbox ? "Sandbox" : "Production"} +

+
+
+
+ ); +}; From 7b87e497180b652072c68f1fa67b90d8ab29e916 Mon Sep 17 00:00:00 2001 From: Ayush Rodrigues Date: Sun, 22 Mar 2026 02:21:27 +0000 Subject: [PATCH 22/49] fix: prevent false sandbox redirect when switching to deployed org Track the last-switched org ID so the redirect effect skips firing when the React Query cache has stale data from a previously active non-deployed org. Also refactors useOrgSwitch to eagerly fetch and set the new org data instead of relying on invalidateQueries alone. Made-with: Cursor --- vite/src/app/layout.tsx | 4 ++- vite/src/hooks/common/useOrg.tsx | 15 +++++--- .../main-sidebar/components/OrgDropdown.tsx | 34 +++++++++++++------ 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/vite/src/app/layout.tsx b/vite/src/app/layout.tsx index ccf7ce97c..8029628c4 100644 --- a/vite/src/app/layout.tsx +++ b/vite/src/app/layout.tsx @@ -10,7 +10,7 @@ import { IconButton } from "@/components/v2/buttons/IconButton"; import { PortalContainerContext } from "@/contexts/PortalContainerContext"; import { useAutumnFlags } from "@/hooks/common/useAutumnFlags"; import { useGlobalErrorHandler } from "@/hooks/common/useGlobalErrorHandler"; -import { useOrg } from "@/hooks/common/useOrg"; +import { getLastSwitchedOrgId, useOrg } from "@/hooks/common/useOrg"; import { useDevQuery } from "@/hooks/queries/useDevQuery"; import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery"; import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery"; @@ -53,6 +53,8 @@ export function MainLayout() { // Redirect to sandbox if not deployed useEffect(() => { if (!orgLoading && org && !org.deployed && env !== AppEnv.Sandbox) { + const lastSwitchedId = getLastSwitchedOrgId(); + if (lastSwitchedId && org.id !== lastSwitchedId) return; const pathname = window.location.pathname; const search = window.location.search; navigate(`/sandbox${pathname}${search}`); diff --git a/vite/src/hooks/common/useOrg.tsx b/vite/src/hooks/common/useOrg.tsx index dc599d322..fc80502af 100644 --- a/vite/src/hooks/common/useOrg.tsx +++ b/vite/src/hooks/common/useOrg.tsx @@ -1,11 +1,17 @@ import type { AppEnv, FrontendOrg } from "@autumn/shared"; -import { useQuery } from "@tanstack/react-query"; +import { keepPreviousData, useQuery } from "@tanstack/react-query"; import { useEffect } from "react"; import { authClient, useListOrganizations } from "@/lib/auth-client"; import { useAxiosInstance } from "@/services/useAxiosInstance"; const ORG_STORAGE_KEY = "autumn_org"; +let lastSwitchedOrgId: string | null = null; +export const setLastSwitchedOrgId = (id: string) => { + lastSwitchedOrgId = id; +}; +export const getLastSwitchedOrgId = () => lastSwitchedOrgId; + /** Clears all org-related localStorage cache entries. Call before reload on session changes (impersonation start/stop). */ export const clearOrgCache = () => { for (const key of Object.keys(localStorage)) { @@ -22,7 +28,6 @@ export const useOrg = (params?: { env?: AppEnv }) => { const fetcher = async () => { try { const { data } = await axiosInstance.get("/organization"); - // Store in local storage if (data) { const storageKey = params?.env ? `${ORG_STORAGE_KEY}_${params.env}` @@ -47,6 +52,8 @@ export const useOrg = (params?: { env?: AppEnv }) => { } }; + const initialDataValue = getInitialData(); + const { data: org, isLoading, @@ -55,12 +62,12 @@ export const useOrg = (params?: { env?: AppEnv }) => { } = useQuery({ queryKey: params?.env ? ["org", params.env] : ["org"], queryFn: fetcher, - initialData: getInitialData(), + initialData: initialDataValue, + placeholderData: keepPreviousData, }); useEffect(() => { const handleNoActiveOrg = async () => { - // 1. If there's existing org, set as active if (orgList && orgList.length > 0) { await authClient.organization.setActive({ organizationId: orgList[0].id, diff --git a/vite/src/views/main-sidebar/components/OrgDropdown.tsx b/vite/src/views/main-sidebar/components/OrgDropdown.tsx index 3a2dbec8a..e680bfe8a 100644 --- a/vite/src/views/main-sidebar/components/OrgDropdown.tsx +++ b/vite/src/views/main-sidebar/components/OrgDropdown.tsx @@ -1,4 +1,3 @@ -import type { FrontendOrg } from "@autumn/shared"; import { DropdownMenuGroup } from "@radix-ui/react-dropdown-menu"; import { useQueryClient } from "@tanstack/react-query"; import { @@ -28,13 +27,18 @@ import { } from "@/components/ui/dropdown-menu"; import { Skeleton } from "@/components/ui/skeleton"; import { useTheme } from "@/contexts/ThemeProvider"; -import { clearOrgCache, useOrg } from "@/hooks/common/useOrg"; +import { + clearOrgCache, + setLastSwitchedOrgId, + useOrg, +} from "@/hooks/common/useOrg"; import { authClient, useListOrganizations, useSession, } from "@/lib/auth-client"; import { cn } from "@/lib/utils"; +import { useAxiosInstance } from "@/services/useAxiosInstance"; import { OrgLogo } from "../org-dropdown/components/OrgLogo"; import { useMemberships } from "../org-dropdown/hooks/useMemberships"; import { useSidebarContext } from "../SidebarContext"; @@ -72,8 +76,8 @@ export const OrgDropdown = () => { <>
- - + +
); @@ -99,7 +103,7 @@ export const OrgDropdown = () => { + )); +} +``` + +The React `useListPlans` hook automatically includes customer context from `AutumnProvider`, so `customerEligibility` is populated on every plan without extra configuration. + +### TypeScript + +```typescript +const { list: plans } = await autumn.plans.list({ + customerId: "user_123", +}); + +for (const plan of plans) { + console.log(plan.name, plan.customerEligibility?.attachAction); + // e.g. "Free" "downgrade", "Pro" "none", "Enterprise" "upgrade" +} +``` + +### Python + +```python +plans = await autumn.plans.list(customer_id="user_123") + +for plan in plans.list: + print(plan.name, plan.customer_eligibility.attach_action) +``` + +--- + +## Switching Plans + +Use `attach` to switch between plans. This handles upgrades, downgrades, and new subscriptions. + +### React + +```tsx +import { useCustomer } from "autumn-js/react"; + +export default function UpgradeButton() { + const { attach } = useCustomer(); + + return ( + + ); +} +``` + +### TypeScript + +```typescript +const response = await autumn.billing.attach({ + customerId: "user_123", + planId: "pro", +}); + +redirect(response.paymentUrl); +``` + +### Python + +```python +response = await autumn.billing.attach( + customer_id="user_123", + plan_id="pro", +) +# Redirect to response.payment_url +``` + +--- + +## Cancelling a Plan + +Cancel a subscription using `billing.update` with a `cancelAction`. + +### React + +```tsx +import { useCustomer } from "autumn-js/react"; + +const { updateSubscription } = useCustomer(); + +// Cancel at end of billing cycle +await updateSubscription({ + planId: "pro", + cancelAction: "cancel_end_of_cycle", +}); +``` + +### TypeScript + +```typescript +await autumn.billing.update({ + customerId: "user_123", + planId: "pro", + cancelAction: "cancel_end_of_cycle", +}); +``` + +### Python + +```python +await autumn.billing.update( + customer_id="user_123", + plan_id="pro", + cancel_action="cancel_end_of_cycle", +) +``` + +--- + +## Uncancelling a Plan + +If a subscription has a pending cancellation (when `canceledAt` is not null while the subscription is still `active`), you can reverse it: + +### React + +```tsx +import { useCustomer } from "autumn-js/react"; + +export default function BillingPage() { + const { data: customer, updateSubscription } = useCustomer(); + + const cancellingSub = customer?.subscriptions.find( + (sub) => sub.status === "active" && sub.canceledAt !== null + ); + + return cancellingSub ? ( + + ) : null; +} +``` + +### TypeScript + +```typescript +const customer = await autumn.customers.getOrCreate({ + customerId: "user_123", +}); + +const cancellingSub = customer.subscriptions?.find( + (sub) => sub.status === "active" && sub.canceledAt !== null +); + +if (cancellingSub) { + await autumn.billing.update({ + customerId: "user_123", + planId: cancellingSub.planId, + cancelAction: "uncancel", + }); +} +``` + +### Python + +```python +customer = await autumn.customers.get_or_create( + customer_id="user_123" +) + +cancelling_sub = next( + (s for s in customer.subscriptions + if s.status == "active" and s.canceled_at is not None), + None, +) + +if cancelling_sub: + await autumn.billing.update( + customer_id="user_123", + plan_id=cancelling_sub.plan_id, + cancel_action="uncancel", + ) +``` + +--- + +## Stripe Billing Portal + +The Stripe billing portal lets users manage their payment method, view past invoices, and cancel their plan. Enable the billing portal in your Stripe settings first. + +### React + +```tsx +import { useCustomer } from "autumn-js/react"; + +const { openCustomerPortal } = useCustomer(); + +await openCustomerPortal({ + returnUrl: "https://your-app.com/billing" +}); +``` + +### TypeScript + +```typescript +const { url } = await autumn.billing.openCustomerPortal({ + customerId: "user_123", + returnUrl: "https://your-app.com/billing", +}); + +redirect(url); +``` + +### Python + +```python +response = await autumn.billing.open_customer_portal( + customer_id="user_123", + return_url="https://your-app.com/billing", +) +# Redirect to response.url +``` + +--- + +## Usage History Chart + +Autumn provides aggregate time series queries for usage data. Pass the response to a charting library like Recharts. + +### React + +```tsx +import { useAggregateEvents } from "autumn-js/react"; + +const { list, total } = useAggregateEvents({ + featureId: "messages", + range: "30d", +}); + +// list: [{ period: 1234567890, values: { messages: 42 } }, ...] +// total: { messages: { count: 100, sum: 500 } } +``` + +### TypeScript + +```typescript +const { list, total } = await autumn.events.aggregate({ + customerId: "user_123", + featureId: "messages", + range: "30d", +}); +``` + +### Python + +```python +response = await autumn.events.aggregate( + customer_id="user_123", + feature_id="messages", + range="30d", +) +# response.list, response.total +``` + +--- + +## Prepaid Top-ups Reference + +Let customers purchase prepaid packages and top-ups. If a user hits a usage limit, they may be willing to purchase a top-up. These are typically one-time purchases that grant a fixed amount of usage. + +### React + +```tsx +import { useCustomer } from "autumn-js/react"; + +export default function TopUpButton() { + const { attach } = useCustomer(); + + return ( + + ); +} +``` + +### TypeScript + +```typescript +const response = await autumn.billing.attach({ + customerId: "user_or_org_id_from_auth", + planId: "top_up", + options: [{ + featureId: "messages", + quantity: 200, + }], +}); + +if (response.paymentUrl) { + redirect(response.paymentUrl); +} +``` + +### Python + +```python +response = await autumn.billing.attach( + customer_id="user_or_org_id_from_auth", + plan_id="top_up", + options=[{ + "feature_id": "messages", + "quantity": 200, + }], +) +``` + +--- + +## Important Notes + +- This handles all upgrades, downgrades, renewals, and uncancellations automatically +- Plan IDs come from the Autumn configuration +- Your Autumn configuration is in `autumn.config.ts` in your project root + +**Docs:** https://docs.useautumn.com/llms.txt diff --git a/packages/atmn-tests/.claude/skills/autumn-gating/SKILL.md b/packages/atmn-tests/.claude/skills/autumn-gating/SKILL.md new file mode 100644 index 000000000..75ed88718 --- /dev/null +++ b/packages/atmn-tests/.claude/skills/autumn-gating/SKILL.md @@ -0,0 +1,261 @@ +--- +name: autumn-gating +description: | + Add usage tracking and feature gating with the Autumn SDK. + Use this skill when asked to: + - Add usage tracking or metering + - Implement feature limits or gating + - Check feature access or entitlements + - Track API calls, messages, or other usage + - Implement credit systems + - Add paywalls or upgrade prompts + - Enforce usage limits server-side +--- + +# Checking and Tracking Usage + +Autumn handles your customer's payments and grants them the features defined in your plan configuration. There are 2 functions you need to enforce limits and gating: + +- `check` for feature access, before allowing a user to do something +- `track` the usage in Autumn afterwards (if needed) + +> Your Autumn configuration is in `autumn.config.ts`. If it doesn't exist, run `npx atmn init` to log in and generate the file. + +## Step 1: Detect Integration Type + +Check if the codebase already has Autumn set up: + +- If there's an `AutumnProvider` and `autumnHandler` mounted -> **React hooks available** (can use for UX) +- Backend SDK should **always** be used to enforce limits server-side + +Report what you detected before proceeding. + +--- + +## Checking Feature Access + +Check if a user has enough remaining balance before executing an action. The `feature_id` used here is defined by you when you create the feature in Autumn. + +### Backend Check (Required for Security) + +**Always check on the backend** before executing any protected action. Frontend checks can be bypassed. + +**TypeScript:** + +```typescript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ + secretKey: process.env.AUTUMN_SECRET_KEY, +}); + +const { allowed } = await autumn.check({ + customerId: "user_or_org_id_from_auth", + featureId: "messages", + requiredBalance: 1, +}); + +if (!allowed) { + console.log("User has run out of messages"); + return; +} +``` + +**Python:** + +```python +from autumn_sdk import Autumn + +autumn = Autumn('am_sk_test_xxx') + +response = await autumn.check( + customer_id="user_or_org_id_from_auth", + feature_id="messages", + required_balance=1, +) + +if not response.allowed: + raise HTTPException(status_code=403, detail="Usage limit reached") +``` + +**cURL:** + +```bash +curl -X POST 'https://api.useautumn.com/v1/check' \ + -H 'Authorization: Bearer am_sk_test_xxx' \ + -H 'Content-Type: application/json' \ + -d '{ + "customer_id": "user_or_org_id_from_auth", + "feature_id": "messages", + "required_balance": 1 + }' +``` + +You can also use `check` to gate boolean features (non-metered features), such as access to "premium AI models". + +### Frontend Check (React Hooks - UX Only) + +When using React hooks, you have access to the customer object which you can use to display billing data. You can use the client-side `check` function to gate features and show paywalls. Permissions are determined by reading the local `data` state, so no call to Autumn's API is made. + +```tsx +import { useCustomer } from "autumn-js/react"; + +export function SendChatMessage() { + const { check, refetch } = useCustomer(); + + const handleSendMessage = async () => { + const { allowed } = check({ featureId: "messages" }); + + if (!allowed) { + alert("You're out of messages"); + } else { + // Send chatbot message + // Then refresh customer usage data + await refetch(); + } + }; +} +``` + +--- + +## Tracking Usage + +After the user has successfully used a feature, record the usage in Autumn. This will decrement their balance. + +**TypeScript:** + +```typescript +await autumn.track({ + customerId: "user_or_org_id_from_auth", + featureId: "messages", + value: 1, +}); +``` + +**Python:** + +```python +await autumn.track( + customer_id="user_or_org_id_from_auth", + feature_id="messages", + value=1, +) +``` + +**cURL:** + +```bash +curl -X POST 'https://api.useautumn.com/v1/track' \ + -H 'Authorization: Bearer am_sk_test_xxx' \ + -H 'Content-Type: application/json' \ + -d '{ + "customer_id": "user_or_org_id_from_auth", + "feature_id": "messages", + "value": 1 + }' +``` + +You should always handle access checks and usage tracking server-side for security. Users can manipulate client-side code using devtools. + +--- + +## Key Concepts + +- **Frontend checks** = UX (show/hide UI, display limits) - can be bypassed by users +- **Backend checks** = Security (enforce limits) - required before any protected action +- **Pattern**: check -> do work -> track (only track after successful completion) +- Feature IDs come from the Autumn configuration +- Current usage and total limit are available from the Customer object + +--- + +## Credit Systems Reference + +Grant users a currency-based balance of credits that various features can draw from. When you have multiple features that cost different amounts, use a credit system to deduct usage from a single balance. + +### Example Case + +AI chatbot product with 2 different models: + +- Basic message: $1 per 100 messages +- Premium message: $10 per 100 messages + +Plans: + +- Free tier: $5 credits per month for free +- Pro tier: $10 credits per month, at $10 per month + +### Checking Access with Credits + +The `required_balance` parameter converts the number of messages to credits. For example, passing `required_balance: 5` for basic messages returns `allowed: true` if the user has at least 0.05 USD credits remaining. + +**Important:** Interact with the underlying features (`basic_messages`, `premium_messages`) - not the credit system directly. + +#### React + +```tsx +import { useCustomer } from "autumn-js/react"; + +export function CheckBasicMessage() { + const { check, refetch } = useCustomer(); + + const handleCheckAccess = async () => { + const { allowed } = check({ featureId: "basic_messages", requiredBalance: 1 }); + + if (!allowed) { + alert("You've run out of basic message credits"); + } else { + // proceed with sending message + await refetch(); + } + }; +} +``` + +#### TypeScript + +```typescript +const { allowed } = await autumn.check({ + customerId: "user_or_org_id_from_auth", + featureId: "basic_messages", + requiredBalance: 1, +}); + +if (!allowed) { + console.log("User has run out of basic message credits"); + return; +} +``` + +#### Python + +```python +response = await autumn.check( + customer_id="user_or_org_id_from_auth", + feature_id="basic_messages", + required_balance=1, +) + +if not response.allowed: + print("User has run out of basic message credits") + return +``` + +### Tracking Usage with Credits + +```typescript +await autumn.track({ + customerId: "user_or_org_id_from_auth", + featureId: "basic_messages", + value: 2, +}); +``` + +This uses 2 basic messages, which costs 0.02 USD credits. + +--- + +**Note:** Autumn configuration is typically in `autumn.config.ts` in the project root. + +**Docs:** https://docs.useautumn.com/llms.txt diff --git a/packages/atmn-tests/.claude/skills/autumn-modelling-pricing-plans/SKILL.md b/packages/atmn-tests/.claude/skills/autumn-modelling-pricing-plans/SKILL.md new file mode 100644 index 000000000..e4e96d65f --- /dev/null +++ b/packages/atmn-tests/.claude/skills/autumn-modelling-pricing-plans/SKILL.md @@ -0,0 +1,500 @@ +--- +name: autumn-modelling-pricing-plans +description: | + Helps design pricing models for Autumn using the autumn.config.ts configuration file. + Use this skill when: + - Designing pricing tiers, plans, or features for Autumn + - Creating autumn.config.ts configuration + - Setting up usage-based, subscription, or credit-based pricing + - Configuring features like API calls, seats, storage, or credits + - Understanding Autumn feature types (metered, boolean, credit_system) + - Working with plan items, metered billing, or tiered pricing +--- + +# Autumn Pricing Model Design + +This guide helps you design your pricing model for Autumn. Autumn uses a configuration file (`autumn.config.ts`) to define your features and plans. + +> **Before starting:** Check for an `autumn.config.ts` in the project root. If it doesn't exist, run `npx atmn init` to log in and generate the file. If you already have a config you want to modify, run `atmn pull` to sync it from Autumn first. + +## Step 1: Understand Your Pricing Needs + +Before building, consider: + +1. What features do you want to offer? (API calls, seats, storage, etc.) +2. What plans do you want? (Free, Pro, etc.) +3. How should usage be measured and limited? + +--- + +## Features + +Features define what can be gated, metered, or billed in your app. + +### `feature(config)` + +| Param | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | string | Yes | Unique identifier used in API calls (`check`, `track`, etc). | +| `name` | string | Yes | Display name shown in the dashboard and billing UI. | +| `type` | enum | Yes | `"boolean"` \| `"metered"` \| `"credit_system"` | +| `consumable` | boolean | For metered | `true` = consumed (messages, API calls), `false` = ongoing (seats, storage). | +| `eventNames` | string[] | No | Event names that trigger this feature. Allows multiple features to respond to a single event. | +| `creditSchema` | array | For credit_system | Maps metered features to credit costs. Each entry: `{ meteredFeatureId, creditCost }`. | + +### Feature Types + +**Boolean** -- simple on/off flag: + +```typescript +export const sso = feature({ + id: 'sso', + name: 'SSO Authentication', + type: 'boolean', +}); +``` + +**Metered, consumable** -- used up and replenished (messages, API calls): + +```typescript +export const messages = feature({ + id: 'messages', + name: 'Messages', + type: 'metered', + consumable: true, +}); +``` + +**Metered, non-consumable** -- ongoing usage (seats, storage): + +```typescript +export const seats = feature({ + id: 'seats', + name: 'Seats', + type: 'metered', + consumable: false, +}); +``` + +**Credit system** -- maps multiple metered features to credit costs: + +```typescript +export const basicModel = feature({ + id: 'basic_model', + name: 'Basic Model', + type: 'metered', + consumable: true, +}); + +export const premiumModel = feature({ + id: 'premium_model', + name: 'Premium Model', + type: 'metered', + consumable: true, +}); + +export const credits = feature({ + id: 'credits', + name: 'AI Credits', + type: 'credit_system', + creditSchema: [ + { meteredFeatureId: basicModel.id, creditCost: 1 }, + { meteredFeatureId: premiumModel.id, creditCost: 5 }, + ], +}); +``` + +If you set the price per credit to 1 cent, credits become monetary credits (eg, 5 credits = $0.05 per premium message). + +--- + +## Plans + +Plans combine features with pricing to create your subscription tiers, add-ons, and top-ups. + +### `plan(config)` + +| Param | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | string | Yes | Unique identifier used in checkout and subscription APIs. | +| `name` | string | Yes | Display name shown in pricing tables and billing. | +| `price` | object | No | Base subscription price: `{ amount, interval }`. | +| `items` | array | No | Array of `item()` objects defining what's included. | +| `autoEnable` | boolean | No | Automatically assign to new customers. Typically used for free plans. | +| `addOn` | boolean | No | Allow purchase alongside other plans (instead of replacing them). | +| `freeTrial` | object | No | `{ durationLength, durationType, cardRequired }`. | +| `group` | string | No | Group related plans. Plans in the same group replace each other on upgrade/downgrade. | + +Price intervals: `"month"` | `"quarter"` | `"semi_annual"` | `"year"` | `"one_off"` + +Trial duration types: `"day"` | `"month"` | `"year"` + +--- + +## Plan Items + +Plan items define what each plan includes -- usage limits, pricing, and billing behavior. + +### `item(config)` + +| Param | Type | Required | Description | +|-------|------|----------|-------------| +| `featureId` | string | Yes | The `id` of the feature to include. | +| `included` | number | No | Amount included for free. Omit for boolean features. | +| `unlimited` | boolean | No | Grant unlimited usage of this feature. | +| `reset` | object | No | How often the included amount resets: `{ interval, intervalCount? }`. | +| `price` | object | No | Pricing for usage beyond the included amount. | +| `proration` | object | No | Mid-cycle changes: `{ onIncrease, onDecrease }`. | +| `rollover` | object | No | Carry unused balance: `{ max, expiryDurationType, expiryDurationLength }`. | + +Reset intervals: `"hour"` | `"day"` | `"week"` | `"month"` | `"quarter"` | `"semi_annual"` | `"year"` + +Proration options: +- `onIncrease`: `"prorate"` | `"charge_immediately"` +- `onDecrease`: `"prorate"` | `"refund_immediately"` | `"no_action"` + +--- + +## Pricing Patterns + +The `price` object on a plan item supports different billing models. + +### Usage-based -- charge based on actual usage + +```typescript +item({ + featureId: seats.id, + included: 5, + price: { + amount: 10, + interval: 'month', + billingMethod: 'usage_based', + billingUnits: 1, + }, +}) +``` + +### Prepaid -- customer buys a fixed quantity upfront + +```typescript +item({ + featureId: credits.id, + price: { + amount: 5, + billingUnits: 100, + billingMethod: 'prepaid', + }, +}) +``` + +### Tiered -- price changes based on usage volume + +```typescript +item({ + featureId: apiCalls.id, + price: { + tiers: [ + { to: 1000, amount: 0.01 }, + { to: 10000, amount: 0.008 }, + { to: 'inf', amount: 0.005 }, + ], + billingMethod: 'usage_based', + interval: 'month', + }, +}) +``` + +### Price Fields Reference + +| Param | Type | Description | +|-------|------|-------------| +| `amount` | number | Price per `billingUnits`. Mutually exclusive with `tiers`. | +| `tiers` | array | Tiered pricing. Each entry: `{ to: number \| "inf", amount }`. Mutually exclusive with `amount`. | +| `billingMethod` | enum | `"usage_based"` \| `"prepaid"`. Required. | +| `interval` | enum | `"week"` \| `"month"` \| `"quarter"` \| `"semi_annual"` \| `"year"`. Omit for one-time charges. | +| `billingUnits` | number | Units per price (default 1). Eg, $5 per 100 credits = `amount: 5, billingUnits: 100`. | +| `maxPurchase` | number | Maximum quantity that can be purchased. | + +--- + +## Common Patterns + +### Free Plan with Usage Limits + +```typescript +export const free = plan({ + id: 'free', + name: 'Free', + autoEnable: true, + items: [ + item({ + featureId: messages.id, + included: 5, + reset: { interval: 'month' }, + }), + item({ + featureId: seats.id, + included: 1, + }), + ], +}); +``` + +### Paid Plan with Flat Fee + Overage + +```typescript +export const pro = plan({ + id: 'pro', + name: 'Pro', + price: { amount: 20, interval: 'month' }, + items: [ + item({ + featureId: messages.id, + included: 1000, + reset: { interval: 'month' }, + price: { + amount: 0.01, + interval: 'month', + billingMethod: 'usage_based', + }, + }), + ], +}); +``` + +### Per-Unit Pricing (e.g., per seat) + +For any "per-X" pricing (like "$Y per seat"), use a base fee + unit allocation: + +```typescript +export const team = plan({ + id: 'team', + name: 'Team', + price: { amount: 10, interval: 'month' }, + items: [ + item({ + featureId: seats.id, + included: 1, + price: { + amount: 10, + interval: 'month', + billingMethod: 'usage_based', + billingUnits: 1, + }, + }), + ], +}); +``` + +This creates: $10/month base price that includes 1 seat, then $10 per additional seat. + +### Plan with Free Trial + +```typescript +export const pro = plan({ + id: 'pro', + name: 'Pro', + price: { amount: 20, interval: 'month' }, + freeTrial: { + durationLength: 14, + durationType: 'day', + cardRequired: true, + }, + items: [ + item({ featureId: messages.id, included: 1000, reset: { interval: 'month' } }), + item({ featureId: sso.id }), + ], +}); +``` + +### Add-on / Top-up (One-time Prepaid) + +```typescript +export const topUp = plan({ + id: 'top_up', + name: 'Message Top-Up', + addOn: true, + items: [ + item({ + featureId: messages.id, + price: { + amount: 5, + billingUnits: 100, + billingMethod: 'prepaid', + }, + }), + ], +}); +``` + +### Annual Plan Variant + +For annual variants, create a separate plan with annual price interval: + +```typescript +export const proAnnual = plan({ + id: 'pro_annual', + name: 'Pro - Annual', + group: 'pro', + price: { amount: 192, interval: 'year' }, + items: [ + item({ featureId: messages.id, included: 1000, reset: { interval: 'month' } }), + ], +}); +``` + +--- + +## Full Example + +A complete config with a free plan, a paid plan with a trial, and a credits top-up add-on: + +```typescript +// autumn.config.ts +import { feature, item, plan } from 'atmn'; + +// Features +export const messages = feature({ + id: 'messages', + name: 'Messages', + type: 'metered', + consumable: true, +}); + +export const seats = feature({ + id: 'seats', + name: 'Seats', + type: 'metered', + consumable: false, +}); + +export const sso = feature({ + id: 'sso', + name: 'SSO', + type: 'boolean', +}); + +// Plans +export const free = plan({ + id: 'free', + name: 'Free', + autoEnable: true, + items: [ + item({ + featureId: messages.id, + included: 5, + reset: { interval: 'month' }, + }), + item({ + featureId: seats.id, + included: 1, + }), + ], +}); + +export const pro = plan({ + id: 'pro', + name: 'Pro', + price: { amount: 20, interval: 'month' }, + freeTrial: { + durationLength: 14, + durationType: 'day', + cardRequired: true, + }, + items: [ + item({ + featureId: messages.id, + included: 1000, + reset: { interval: 'month' }, + }), + item({ + featureId: seats.id, + included: 5, + price: { + amount: 10, + interval: 'month', + billingMethod: 'usage_based', + billingUnits: 1, + }, + }), + item({ + featureId: sso.id, + }), + ], +}); + +export const topUp = plan({ + id: 'top_up', + name: 'Message Top-Up', + addOn: true, + items: [ + item({ + featureId: messages.id, + price: { + amount: 5, + billingUnits: 100, + billingMethod: 'prepaid', + }, + }), + ], +}); +``` + +--- + +## Guidelines + +### Start Simple + +- If the user describes more than 3 features, start with the 3 most important (prioritize metered features) and ask them to confirm before adding more +- Inform them you kept it simple to start with, but they can add more later + +### Disambiguate Pricing Model + +- When the user mentions a price for a feature, ask whether it should be **usage-based** (pay as you go, billed at the end of the cycle) or **prepaid** (buy a fixed quantity upfront) +- Don't assume one or the other without asking + +### Don't Fabricate Capabilities + +- If the user asks about pricing or functionality you're not sure Autumn supports, do NOT make it up or assume it can be done +- Point them to Discord (https://discord.gg/atmn) or docs (https://docs.useautumn.com/llms.txt) instead + +### Naming Conventions + +- Feature and plan IDs should be lowercase with underscores (e.g., `pro_plan`, `chat_messages`) + +### Features vs Plan Items + +- Features define WHAT can be tracked (e.g., "credits") +- Plan items define HOW a feature is granted in a plan (recurring, one-time, free, paid) +- Never create duplicate features for the same underlying resource + - Example: "monthly tokens" and "one-time tokens" should be the SAME feature ("tokens"), referenced by different plan items with different intervals + +### Default Plans + +- **Never** set `autoEnable: true` for plans with prices +- Default plans must be free + +### Enterprise Plans + +- Ignore "Enterprise" plans with custom pricing in the config +- Custom plans can be created per-customer in the Autumn dashboard + +### Currency + +- Currency can be changed in the Autumn dashboard under Developer > Stripe + +## Previewing and Pushing Changes + +After updating `autumn.config.ts`: + +1. **Preview first**: Run `atmn preview` to lint, validate and preview your plans. Show the output to the user so they can review the full configuration. +2. **Get confirmation**: Ask the user to review, edit, and confirm the preview output before pushing. Do NOT push until the user explicitly confirms. +3. **Push**: Once the user is happy, run `atmn push` to sync the configuration to Autumn. +4. Test in sandbox mode before going live. You can push to production with `atmn push -p`. + +## Resources + +- Discord support: https://discord.gg/atmn (very responsive) +- Documentation: https://docs.useautumn.com +- LLM-friendly docs: https://docs.useautumn.com/llms.txt diff --git a/packages/atmn-tests/.claude/skills/autumn-setup/SKILL.md b/packages/atmn-tests/.claude/skills/autumn-setup/SKILL.md new file mode 100644 index 000000000..7fb588215 --- /dev/null +++ b/packages/atmn-tests/.claude/skills/autumn-setup/SKILL.md @@ -0,0 +1,339 @@ +--- +name: autumn-setup +description: | + Sets up Autumn billing integration: installs the SDK, creates a customer, and adds the payment flow. + Use this skill when the user wants to: + - Set up Autumn billing + - Create an Autumn customer + - Integrate Autumn into their app + - Add billing/entitlements with Autumn + - Configure Autumn SDK + - Add payment flow or checkout +--- + +# Set up Autumn Billing + +Autumn is a billing and entitlements layer over Stripe. This skill walks through installing the SDK, creating an Autumn customer, and wiring up the payment flow. + +> **Before starting:** Check for an `autumn.config.ts` in the project root. If it doesn't exist, run `npx atmn init` to log in and generate the file (this saves your API key and syncs your config). Then refer to `autumn.config.ts` for your product and feature IDs. + +## Step 1: Analyze the Codebase + +Before making changes, detect: + +- **Language**: TypeScript/JavaScript, Python, or other +- **If TS/JS - Framework**: Next.js, Hono, or other +- **If TS/JS - React frontend?**: Check for React in package.json +- **Customer model**: Look at the auth setup to determine whether customers map to individual users or organizations. Check for org/team/workspace models in the codebase. + +If it's clear from the codebase (e.g., there's an org model and team-based auth), state your assumption. If it's ambiguous, ask the user: + +> **Should Autumn customers be individual users, or organizations?** +> - **Users (B2C)**: Each user has their own plan and limits +> - **Organizations (B2B)**: Plans and limits are shared across an org + +## Step 2: Create a Plan and Confirm + +Before writing any integration code, present a short plan to the user covering: + +- What stack/framework you detected +- Whether customers are users or orgs (and why you think so) +- Which path you're following (React fullstack vs backend-only) +- Which files you'll create or modify +- Where the handler / provider / customer creation will go +- Where the payment flow will be wired up + +Ask the user to **read, edit, and confirm** the plan before proceeding. Do NOT start coding until the user approves. + +--- + +## Path A: React + Node.js (Fullstack TypeScript) + +Use this path if there's a React frontend with a Node.js backend. + +### A1. Install the SDK + +Use the package manager already installed (npm, yarn, pnpm, bun): + +```bash +npm install autumn-js +``` + +### A2. Mount the Handler (Server-Side) + +This creates endpoints at `/api/autumn/*` that the React hooks will call. The `identify` function should return either the user ID or org ID from your auth provider, depending on how you're using Autumn. + +#### Next.js (App Router) + +```typescript +// app/api/autumn/[...all]/route.ts +import { autumnHandler } from "autumn-js/next"; + +export const { GET, POST } = autumnHandler({ + identify: async (request) => { + // Get user/org from your auth provider + const session = await auth.api.getSession({ headers: request.headers }); + return { + customerId: session?.user.id, // or session?.org.id for B2B + customerData: { + name: session?.user.name, + email: session?.user.email, + }, + }; + }, +}); +``` + +#### Hono + +```typescript +import { autumnHandler } from "autumn-js/hono"; + +app.use("/api/autumn/*", autumnHandler({ + identify: async (c) => { + const session = await auth.api.getSession({ headers: c.req.raw.headers }); + return { + customerId: session?.user.id, // or session?.org.id for B2B + customerData: { name: session?.user.name, email: session?.user.email }, + }; + }, +})); +``` + +#### Other Frameworks (Generic Handler) + +For any framework not listed above, use the generic handler: + +```typescript +import { autumnHandler } from "autumn-js/backend"; + +// Mount this handler onto the /api/autumn/* path in your backend +const handleRequest = async (request) => { + const session = await auth.api.getSession({ headers: request.headers }); + + let body = null; + if (request.method !== "GET") { + body = await request.json(); + } + + const { statusCode, response } = await autumnHandler({ + customerId: session?.user.id, + customerData: { + name: session?.user.name, + email: session?.user.email, + }, + request: { + url: request.url, + method: request.method, + body: body, + }, + }); + + return new Response(JSON.stringify(response), { + status: statusCode, + headers: { "Content-Type": "application/json" }, + }); +}; +``` + +### A3. Add the Provider (Client-Side) + +Wrap your app with `AutumnProvider`: + +```tsx +import { AutumnProvider } from "autumn-js/react"; + +export default function RootLayout({ children }) { + return ( + + {children} + + ); +} +``` + +If your backend is on a different URL (e.g., Vite + separate server), pass `backendUrl`: + +```tsx + +``` + +### A4. Create a Customer + +Add this hook to any component. It automatically creates an Autumn customer for new users and fetches existing customer state: + +```tsx +import { useCustomer } from "autumn-js/react"; + +const { data } = useCustomer(); +console.log("Autumn customer:", data); +``` + +Autumn's customer ID is the same as your internal user or org ID from your auth provider. No need to store any extra IDs. + +### A5. Stripe Payment Flow + +Call `attach` when the customer wants to purchase a plan. This returns a Stripe payment URL. Once they pay, Autumn grants access to the features defined in the plan. + +```tsx +import { useCustomer } from "autumn-js/react"; + +export default function PurchaseButton() { + const { attach } = useCustomer(); + + return ( + + ); +} +``` + +This handles all plan change scenarios (upgrades, downgrades, one-time topups, renewals, etc). + +The `redirectMode: "always"` flag always returns a payment URL: +- New purchases redirect to Stripe Checkout to enter payment details +- Subsequent charges redirect to an Autumn hosted, one-click confirmation page + +--- + +## Path B: Backend Only (Node.js, Python, or Other) + +Use this path if there's no React frontend, or you prefer server-side only. + +### B1. Install the SDK + +**Node.js:** + +```bash +npm install autumn-js +``` + +**Python:** + +```bash +pip install autumn-sdk +``` + +### B2. Initialize the Client + +**TypeScript/JavaScript:** + +```typescript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ + secretKey: process.env.AUTUMN_SECRET_KEY, +}); +``` + +**Python:** + +```python +from autumn_sdk import Autumn + +autumn = Autumn('am_sk_test_xxx') +``` + +### B3. Create a Customer + +When the customer signs up, create an Autumn customer. Autumn will automatically enable any `autoEnable` plan (typically Free). + +**TypeScript:** + +```typescript +const customer = await autumn.customers.getOrCreate({ + customerId: "user_or_org_id_from_auth", + name: "John Doe", + email: "john@example.com", +}); +``` + +**Python:** + +```python +customer = await autumn.customers.get_or_create( + customer_id="user_or_org_id_from_auth", + name="John Doe", + email="john@example.com", +) +``` + +**cURL:** + +```bash +curl -X POST https://api.useautumn.com/v1/customers \ + -H "Authorization: Bearer am_sk_test_xxx" \ + -H "Content-Type: application/json" \ + -d '{"customer_id": "user_or_org_id_from_auth", "name": "John Doe", "email": "john@example.com"}' +``` + +Autumn's customer ID is the same as your internal user or org ID from your auth provider. No need to store any extra IDs. + +### B4. Stripe Payment Flow + +Call `attach` when the customer wants to purchase a plan. This returns a Stripe payment URL. Redirect the customer to complete payment. + +**TypeScript:** + +```typescript +const response = await autumn.billing.attach({ + customerId: "user_or_org_id_from_auth", + planId: "pro", + redirectMode: "always", +}); + +redirect(response.paymentUrl); +``` + +**Python:** + +```python +response = await autumn.billing.attach( + customer_id="user_or_org_id_from_auth", + plan_id="pro", + redirect_mode="always", +) +# Redirect to response.payment_url +``` + +**cURL:** + +```bash +curl -X POST 'https://api.useautumn.com/v1/attach' \ + -H 'Authorization: Bearer am_sk_test_xxx' \ + -H 'Content-Type: application/json' \ + -d '{ + "customer_id": "user_or_org_id_from_auth", + "plan_id": "pro", + "redirect_mode": "always" + }' +``` + +This handles all plan change scenarios (upgrades, downgrades, one-time topups, renewals, etc). + +The `redirectMode: "always"` flag always returns a payment URL: +- New purchases redirect to Stripe Checkout to enter payment details +- Subsequent charges redirect to an Autumn hosted, one-click confirmation page + +--- + +## Verification + +After setup, report to the user: + +1. What stack you detected +2. Which path you followed +3. What files you created/modified +4. That the Autumn customer is logged in browser, and to check in the Autumn dashboard + +**Note:** Your Autumn configuration is in `autumn.config.ts` in your project root. + +**Documentation:** https://docs.useautumn.com/llms.txt diff --git a/packages/atmn-tests/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc b/packages/atmn-tests/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc new file mode 120000 index 000000000..6100270f8 --- /dev/null +++ b/packages/atmn-tests/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc @@ -0,0 +1 @@ +../../CLAUDE.md \ No newline at end of file diff --git a/packages/atmn-tests/.gitignore b/packages/atmn-tests/.gitignore new file mode 100644 index 000000000..a14702c40 --- /dev/null +++ b/packages/atmn-tests/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/atmn-tests/@useautumn-sdk.d.ts b/packages/atmn-tests/@useautumn-sdk.d.ts new file mode 100644 index 000000000..3acfa889c --- /dev/null +++ b/packages/atmn-tests/@useautumn-sdk.d.ts @@ -0,0 +1,14 @@ +// AUTO-GENERATED by atmn pull +// DO NOT EDIT MANUALLY + +declare module '@useautumn/sdk' { + // Features + export const messages: Feature; + + // Plans + export const free: Plan; + + // Base types + export type Feature = import('./autumn.config').Feature; + export type Plan = import('./autumn.config').Plan; +} diff --git a/packages/atmn-tests/CLAUDE.md b/packages/atmn-tests/CLAUDE.md new file mode 100644 index 000000000..ebda99514 --- /dev/null +++ b/packages/atmn-tests/CLAUDE.md @@ -0,0 +1,111 @@ +--- +description: Use Bun instead of Node.js, npm, pnpm, or vite. +globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json" +alwaysApply: false +--- + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; +import { createRoot } from "react-dom/client"; + +// import .css files directly and it works +import './index.css'; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`. diff --git a/packages/atmn-tests/README.md b/packages/atmn-tests/README.md new file mode 100644 index 000000000..1f72e7f7b --- /dev/null +++ b/packages/atmn-tests/README.md @@ -0,0 +1,15 @@ +# atmn-tests + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.10. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/packages/atmn-tests/autumn.config.ts b/packages/atmn-tests/autumn.config.ts new file mode 100644 index 000000000..381a27a5e --- /dev/null +++ b/packages/atmn-tests/autumn.config.ts @@ -0,0 +1,24 @@ +import { feature, item, plan } from "atmn"; + +// Features +export const messages = feature({ + id: 'messages', + name: 'Messages', + type: 'metered', + consumable: true, +}); + +// Plans +export const free = plan({ + id: 'free', + name: 'Free', + items: [ + item({ + featureId: messages.id, + included: 10, + reset: { + interval: 'one_off', + }, + }), + ], +}); diff --git a/packages/atmn-tests/bun.lock b/packages/atmn-tests/bun.lock new file mode 100644 index 000000000..3ab22141c --- /dev/null +++ b/packages/atmn-tests/bun.lock @@ -0,0 +1,26 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "atmn-tests", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], + + "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + } +} diff --git a/packages/atmn-tests/index.ts b/packages/atmn-tests/index.ts new file mode 100644 index 000000000..f67b2c645 --- /dev/null +++ b/packages/atmn-tests/index.ts @@ -0,0 +1 @@ +console.log("Hello via Bun!"); \ No newline at end of file diff --git a/packages/atmn-tests/package.json b/packages/atmn-tests/package.json new file mode 100644 index 000000000..03792c8ad --- /dev/null +++ b/packages/atmn-tests/package.json @@ -0,0 +1,13 @@ +{ + "name": "atmn-tests", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "atmn": "workspace:*", + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + } +} diff --git a/packages/atmn-tests/tsconfig.json b/packages/atmn-tests/tsconfig.json new file mode 100644 index 000000000..bfa0fead5 --- /dev/null +++ b/packages/atmn-tests/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/packages/atmn/package.json b/packages/atmn/package.json index 2a6d34b15..596602819 100644 --- a/packages/atmn/package.json +++ b/packages/atmn/package.json @@ -44,7 +44,6 @@ "README.md" ], "dependencies": { - "@inkjs/ui": "^2.0.0", "@inquirer/prompts": "^7.6.0", "@mishieck/ink-titled-box": "^0.3.0", "@tanstack/react-query": "^5.90.17", diff --git a/packages/atmn/src/views/react/components/MultiSelect.tsx b/packages/atmn/src/views/react/components/MultiSelect.tsx new file mode 100644 index 000000000..619844e5c --- /dev/null +++ b/packages/atmn/src/views/react/components/MultiSelect.tsx @@ -0,0 +1,97 @@ +import { Box, Text, useInput } from "ink"; +import { useMemo, useState } from "react"; + +interface MultiSelectOption { + label: string; + value: string; +} + +interface MultiSelectProps { + options: MultiSelectOption[]; + defaultValue?: string[]; + visibleOptionCount?: number; + onChange?: (values: string[]) => void; + onSubmit?: (values: string[]) => void; +} + +const POINTER = "\u276F"; +const TICK = "\u2714"; +const CIRCLE = "\u25CB"; + +export function MultiSelect({ + options, + defaultValue = [], + visibleOptionCount = 5, + onChange, + onSubmit, +}: MultiSelectProps) { + const effectiveVisibleCount = Math.min(visibleOptionCount, options.length); + + const [focusedIndex, setFocusedIndex] = useState(0); + const [selectedValues, setSelectedValues] = useState>( + () => new Set(defaultValue), + ); + const [visibleFrom, setVisibleFrom] = useState(0); + + const visibleOptions = useMemo(() => { + return options.slice(visibleFrom, visibleFrom + effectiveVisibleCount); + }, [options, visibleFrom, effectiveVisibleCount]); + + useInput((input, key) => { + if (key.downArrow) { + const nextIndex = Math.min(focusedIndex + 1, options.length - 1); + setFocusedIndex(nextIndex); + const visibleTo = visibleFrom + effectiveVisibleCount; + if (nextIndex >= visibleTo) { + setVisibleFrom(nextIndex - effectiveVisibleCount + 1); + } + } + + if (key.upArrow) { + const prevIndex = Math.max(focusedIndex - 1, 0); + setFocusedIndex(prevIndex); + if (prevIndex < visibleFrom) { + setVisibleFrom(prevIndex); + } + } + + if (input === " ") { + const focusedOption = options[focusedIndex]; + if (!focusedOption) return; + + const newSelected = new Set(selectedValues); + if (newSelected.has(focusedOption.value)) { + newSelected.delete(focusedOption.value); + } else { + newSelected.add(focusedOption.value); + } + setSelectedValues(newSelected); + onChange?.([...newSelected]); + } + + if (key.return) { + onSubmit?.([...selectedValues]); + } + }); + + return ( + + {visibleOptions.map((option, i) => { + const absoluteIndex = visibleFrom + i; + const isFocused = absoluteIndex === focusedIndex; + const isSelected = selectedValues.has(option.value); + + return ( + + + {isFocused ? POINTER : " "}{" "} + + + {isSelected ? TICK : CIRCLE} {option.label} + + + ); + })} + + ); +} diff --git a/packages/atmn/src/views/react/components/ProgressBar.tsx b/packages/atmn/src/views/react/components/ProgressBar.tsx new file mode 100644 index 000000000..aa28433fe --- /dev/null +++ b/packages/atmn/src/views/react/components/ProgressBar.tsx @@ -0,0 +1,23 @@ +import { Box, Text } from "ink"; + +interface ProgressBarProps { + value: number; +} + +/** Simple progress bar for terminal display. Value is 0-100. */ +export function ProgressBar({ value }: ProgressBarProps) { + const clamped = Math.max(0, Math.min(100, value)); + const width = 20; + const filled = Math.round((clamped / 100) * width); + const empty = width - filled; + + const color = clamped >= 90 ? "red" : clamped >= 70 ? "yellow" : "green"; + + return ( + + {"█".repeat(filled)} + {"░".repeat(empty)} + {clamped}% + + ); +} diff --git a/packages/atmn/src/views/react/components/index.ts b/packages/atmn/src/views/react/components/index.ts index 4bce2f567..3c04f0363 100644 --- a/packages/atmn/src/views/react/components/index.ts +++ b/packages/atmn/src/views/react/components/index.ts @@ -6,6 +6,8 @@ export { export { Card } from "./Card.js"; export { KeyValue } from "./KeyValue.js"; export { LoadingText } from "./LoadingText.js"; +export { MultiSelect } from "./MultiSelect.js"; +export { ProgressBar } from "./ProgressBar.js"; export { PromptCard } from "./PromptCard.js"; export { CardWidthProvider } from "./providers/CardWidthContext.js"; export { diff --git a/packages/atmn/src/views/react/customers/components/CustomerSheet.tsx b/packages/atmn/src/views/react/customers/components/CustomerSheet.tsx index 48b1a9167..c9b0c309f 100644 --- a/packages/atmn/src/views/react/customers/components/CustomerSheet.tsx +++ b/packages/atmn/src/views/react/customers/components/CustomerSheet.tsx @@ -1,5 +1,5 @@ -import { Spinner } from "@inkjs/ui"; import { Box, Text } from "ink"; +import Spinner from "ink-spinner"; import type { ApiBalance, CustomerSheetProps } from "../types.js"; import { formatDate } from "../types.js"; import { @@ -89,7 +89,9 @@ export function CustomerSheet({ {/* Loading state for expanded data */} {isLoadingExpanded && ( - + + Loading details... + )} diff --git a/packages/atmn/src/views/react/customers/components/sections/BalancesSection.tsx b/packages/atmn/src/views/react/customers/components/sections/BalancesSection.tsx index 57d8dfd52..7f696fdc5 100644 --- a/packages/atmn/src/views/react/customers/components/sections/BalancesSection.tsx +++ b/packages/atmn/src/views/react/customers/components/sections/BalancesSection.tsx @@ -1,5 +1,5 @@ -import { ProgressBar } from "@inkjs/ui"; import { Box, Text } from "ink"; +import { ProgressBar } from "../../../components/index.js"; import type { ApiBalance } from "../../types.js"; export interface BalancesSectionProps { diff --git a/packages/atmn/src/views/react/init/steps/AgentStep.tsx b/packages/atmn/src/views/react/init/steps/AgentStep.tsx index bef52004e..26e230e72 100644 --- a/packages/atmn/src/views/react/init/steps/AgentStep.tsx +++ b/packages/atmn/src/views/react/init/steps/AgentStep.tsx @@ -1,4 +1,3 @@ -import { MultiSelect } from "@inkjs/ui"; import { Box, Text } from "ink"; import { useEffect, useState } from "react"; import { @@ -6,7 +5,11 @@ import { type FileOption, useAgentSetup, } from "../../../../lib/hooks/index.js"; -import { StatusLine, StepHeader } from "../../components/index.js"; +import { + MultiSelect, + StatusLine, + StepHeader, +} from "../../components/index.js"; interface AgentStepProps { step: number; diff --git a/packages/atmn/src/views/react/init/steps/HandoffStep.tsx b/packages/atmn/src/views/react/init/steps/HandoffStep.tsx index 57f1e3962..130d2534c 100644 --- a/packages/atmn/src/views/react/init/steps/HandoffStep.tsx +++ b/packages/atmn/src/views/react/init/steps/HandoffStep.tsx @@ -1,9 +1,14 @@ -import { MultiSelect, TextInput } from "@inkjs/ui"; import { Box, Text, useApp } from "ink"; +import TextInput from "ink-text-input"; import open from "open"; import React, { useState } from "react"; import { useClipboard, useCreateSkills } from "../../../../lib/hooks/index.js"; -import { SelectMenu, StatusLine, StepHeader } from "../../components/index.js"; +import { + MultiSelect, + SelectMenu, + StatusLine, + StepHeader, +} from "../../components/index.js"; // System prompt for AI integration - will be copied to clipboard const SYSTEM_PROMPT = `You are an expert AI assistant that helps users set up Autumn, a billing and entitlements layer over Stripe. The user has already installed Autumn Skills ready for you to use the load skill tool. @@ -239,7 +244,7 @@ export function HandoffStep({ {">"} From 19f97783e56b4493226c408f8eb431375ed3d994 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:12:58 +0000 Subject: [PATCH 28/49] =?UTF-8?q?fix:=20=F0=9F=90=9B=20sync=20pkg=20versio?= =?UTF-8?q?n=20for=20atmn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/atmn/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/atmn/package.json b/packages/atmn/package.json index 596602819..8df6d9aec 100644 --- a/packages/atmn/package.json +++ b/packages/atmn/package.json @@ -1,6 +1,6 @@ { "name": "atmn", - "version": "1.1.5", + "version": "1.1.6", "license": "MIT", "bin": { "atmn": "dist/cli.js" From d96726fd7f963212123266aef701fd69c55b436a Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 23 Mar 2026 10:13:10 +0000 Subject: [PATCH 29/49] commented in stripe client cache --- .../src/external/connect/clientCache/stripeClientCache.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/external/connect/clientCache/stripeClientCache.ts b/server/src/external/connect/clientCache/stripeClientCache.ts index b736e0c96..13369dc1a 100644 --- a/server/src/external/connect/clientCache/stripeClientCache.ts +++ b/server/src/external/connect/clientCache/stripeClientCache.ts @@ -16,10 +16,10 @@ export const getOrCreateStripeClient = ({ cacheKey: string; create: () => Stripe; }): Stripe => { - // const cached = stripeClientCache.get(cacheKey); - // if (cached) return cached; + const cached = stripeClientCache.get(cacheKey); + if (cached) return cached; const client = create(); - // stripeClientCache.set(cacheKey, client); + stripeClientCache.set(cacheKey, client); return client; }; From 43421d03f1f4b7caa094f951d3c7f0c467313fd7 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:17:19 +0000 Subject: [PATCH 30/49] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20remove=20debug=20l?= =?UTF-8?q?ogs=20from=20atmn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/atmn/package.json | 2 +- packages/atmn/src/commands/push/push.ts | 103 ++++++++++++------------ 2 files changed, 52 insertions(+), 53 deletions(-) diff --git a/packages/atmn/package.json b/packages/atmn/package.json index 8df6d9aec..4aadf4ed1 100644 --- a/packages/atmn/package.json +++ b/packages/atmn/package.json @@ -1,6 +1,6 @@ { "name": "atmn", - "version": "1.1.6", + "version": "1.1.7", "license": "MIT", "bin": { "atmn": "dist/cli.js" diff --git a/packages/atmn/src/commands/push/push.ts b/packages/atmn/src/commands/push/push.ts index 49dd7ec43..fe3480669 100644 --- a/packages/atmn/src/commands/push/push.ts +++ b/packages/atmn/src/commands/push/push.ts @@ -23,7 +23,10 @@ import { transformApiFeature, transformApiPlan, } from "../../lib/transforms/apiToSdk/index.js"; -import { transformFeatureToApi, transformPlanToApi } from "../../lib/transforms/sdkToApi/index.js"; +import { + transformFeatureToApi, + transformPlanToApi, +} from "../../lib/transforms/sdkToApi/index.js"; import type { FeatureDeleteInfo, PlanDeleteInfo, @@ -156,10 +159,9 @@ async function checkPlanForVersioning( }; } - const missingFeatureIds = - (plan.items || []) - .map((item) => item.featureId) - .filter((featureId) => !remoteFeatureIds.has(featureId)); + const missingFeatureIds = (plan.items || []) + .map((item) => item.featureId) + .filter((featureId) => !remoteFeatureIds.has(featureId)); const missingLocalFeatureIds = missingFeatureIds.filter((featureId) => localFeatureIds.has(featureId), @@ -169,15 +171,15 @@ async function checkPlanForVersioning( ); if (missingLocalFeatureIds.length > 0) { - if (missingUnknownFeatureIds.length > 0) { - console.log( - `[checkPlanForVersioning] plan=${plan.id} has mixed missing features. Local-first features: ${missingLocalFeatureIds.join(", ")}; missing unknown: ${missingUnknownFeatureIds.join(", ")}.`, - ); - } else { - console.log( - `[checkPlanForVersioning] plan=${plan.id} has local-only feature refs (${missingLocalFeatureIds.join(", ")}). Deferring versioning check until after feature upsert.`, - ); - } + // if (missingUnknownFeatureIds.length > 0) { + // console.log( + // `[checkPlanForVersioning] plan=${plan.id} has mixed missing features. Local-first features: ${missingLocalFeatureIds.join(", ")}; missing unknown: ${missingUnknownFeatureIds.join(", ")}.`, + // ); + // } else { + // console.log( + // `[checkPlanForVersioning] plan=${plan.id} has local-only feature refs (${missingLocalFeatureIds.join(", ")}). Deferring versioning check until after feature upsert.`, + // ); + // } return { plan, @@ -209,9 +211,10 @@ async function checkPlanForVersioning( const responseMessage = (response && (response.message as string | undefined)) || ""; - const missingFeatureMatch = /Feature\s+["']?([a-zA-Z0-9_-]+)["']?\s+not\s+found/i.exec( - responseMessage, - ); + const missingFeatureMatch = + /Feature\s+["']?([a-zA-Z0-9_-]+)["']?\s+not\s+found/i.exec( + responseMessage, + ); const missingFeature = response?.feature || response?.feature_id || missingFeatureMatch?.[1]; @@ -223,19 +226,19 @@ async function checkPlanForVersioning( responseMessage, ) ) { - if (missingUnknownFeatureIds.length > 0) { - console.log( - `[checkPlanForVersioning] plan=${plan.id} failed versioning check: feature "${missingFeature || "unknown"}" not found and not in local config`, - ); - } else if (missingFeature) { - console.log( - `[checkPlanForVersioning] plan=${plan.id} deferring versioning check due feature_not_found for feature "${missingFeature}", will recheck after feature upsert`, - ); - } else { - console.log( - `[checkPlanForVersioning] plan=${plan.id} deferring versioning check due feature_not_found`, - ); - } + // if (missingUnknownFeatureIds.length > 0) { + // console.log( + // `[checkPlanForVersioning] plan=${plan.id} failed versioning check: feature "${missingFeature || "unknown"}" not found and not in local config`, + // ); + // } else if (missingFeature) { + // console.log( + // `[checkPlanForVersioning] plan=${plan.id} deferring versioning check due feature_not_found for feature "${missingFeature}", will recheck after feature upsert`, + // ); + // } else { + // console.log( + // `[checkPlanForVersioning] plan=${plan.id} deferring versioning check due feature_not_found`, + // ); + // } return { plan, @@ -339,9 +342,7 @@ function normalizeFeatureForCompare(f: Feature): Record { if (f.creditSchema && f.creditSchema.length > 0) { result.creditSchema = [...f.creditSchema] - .sort((a, b) => - a.meteredFeatureId.localeCompare(b.meteredFeatureId), - ) + .sort((a, b) => a.meteredFeatureId.localeCompare(b.meteredFeatureId)) .map((cs) => ({ meteredFeatureId: cs.meteredFeatureId, creditCost: cs.creditCost, @@ -355,9 +356,7 @@ function normalizeFeatureForCompare(f: Feature): Record { * Normalize a plan item to a canonical form for comparison. * Strips default values (unlimited: false, billingUnits: 1, intervalCount: 1). */ -function normalizePlanFeatureForCompare( - pf: PlanItem, -): Record { +function normalizePlanFeatureForCompare(pf: PlanItem): Record { const f = pf as Record; const result: Record = { featureId: pf.featureId, @@ -379,8 +378,7 @@ function normalizePlanFeatureForCompare( if (price != null) { const p: Record = {}; if (price.amount != null) p.amount = price.amount; - if (price.billingMethod != null) - p.billingMethod = price.billingMethod; + if (price.billingMethod != null) p.billingMethod = price.billingMethod; if (price.interval != null) p.interval = price.interval; if (price.intervalCount != null && price.intervalCount !== 1) { p.intervalCount = price.intervalCount; @@ -493,9 +491,7 @@ export async function analyzePush( const localFeatureIds = new Set(localFeatures.map((f) => f.id)); const localPlanIds = new Set(localPlans.map((p) => p.id)); - const remoteFeaturesById = new Map( - remoteData.features.map((f) => [f.id, f]), - ); + const remoteFeaturesById = new Map(remoteData.features.map((f) => [f.id, f])); const remotePlansById = new Map(remoteData.plans.map((p) => [p.id, p])); const localPlansById = new Map(localPlans.map((p) => [p.id, p])); @@ -514,15 +510,14 @@ export async function analyzePush( const archivedFeatures = localFeatures.filter((f) => { const remote = remoteFeaturesById.get(f.id); const localArchived = (f as Feature & { archived?: boolean }).archived; - const remoteArchived = remote && (remote as Feature & { archived?: boolean }).archived; + const remoteArchived = + remote && (remote as Feature & { archived?: boolean }).archived; // Prompt to unarchive only if remote is archived but local doesn't explicitly want it archived return remoteArchived && !localArchived; }); // Find plans to create and update (only actually changed plans) - const plansToCreate = localPlans.filter( - (p) => !remotePlansById.has(p.id), - ); + const plansToCreate = localPlans.filter((p) => !remotePlansById.has(p.id)); const plansToUpdateLocal = localPlans.filter((p) => { const remotePlan = remotePlansById.get(p.id); if (!remotePlan) return false; @@ -532,7 +527,12 @@ export async function analyzePush( // Check versioning info for each plan to update const remoteFeatureIds = new Set(remoteData.features.map((f) => f.id)); const planUpdatePromises = plansToUpdateLocal.map((plan) => - checkPlanForVersioning(plan, remoteData.plans, localFeatureIds, remoteFeatureIds), + checkPlanForVersioning( + plan, + remoteData.plans, + localFeatureIds, + remoteFeatureIds, + ), ); const plansToUpdate = await Promise.all(planUpdatePromises); @@ -556,7 +556,8 @@ export async function analyzePush( const archivedPlans = localPlans.filter((p) => { const remote = remotePlansById.get(p.id); const localArchived = (p as Plan & { archived?: boolean }).archived; - const remoteArchived = remote && (remote as Plan & { archived?: boolean }).archived; + const remoteArchived = + remote && (remote as Plan & { archived?: boolean }).archived; // Prompt to unarchive only if remote is archived but local doesn't explicitly want it archived return remoteArchived && !localArchived; }); @@ -593,10 +594,7 @@ export async function analyzePush( if (plansRemovingFeature) { plansRemovingFeature.add(remotePlan.id); } else { - plansRemovingFeatureById.set( - featureId, - new Set([remotePlan.id]), - ); + plansRemovingFeatureById.set(featureId, new Set([remotePlan.id])); } } } @@ -626,7 +624,8 @@ export async function analyzePush( return info; } - const remotePlansForFeature = remoteFeaturePlanRefs.get(info.id) ?? new Set(); + const remotePlansForFeature = + remoteFeaturePlanRefs.get(info.id) ?? new Set(); let hasBlockingPlan = false; for (const planId of remotePlansForFeature) { From 6e4154a7577d414fac970f3857dd605a528ce4cd Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 23 Mar 2026 10:28:14 +0000 Subject: [PATCH 31/49] fix: minor issues with stripe client caching --- .../connect/clientCache/cacheKeyUtils.ts | 12 +++-- server/src/external/connect/initStripeCli.ts | 1 + server/src/initHono.ts | 3 +- server/src/internal/debug/debugRouter.ts | 44 +++++++++---------- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/server/src/external/connect/clientCache/cacheKeyUtils.ts b/server/src/external/connect/clientCache/cacheKeyUtils.ts index 9d49aab6d..c544fc7fe 100644 --- a/server/src/external/connect/clientCache/cacheKeyUtils.ts +++ b/server/src/external/connect/clientCache/cacheKeyUtils.ts @@ -1,5 +1,9 @@ import type { AppEnv } from "@autumn/shared"; +const getSecretFingerprint = ({ secret }: { secret: string }) => { + return Bun.hash(secret).toString(); +}; + /** Builds cache key for Stripe clients created via org secret key. */ export const buildSecretKeyCacheKey = ({ orgId, @@ -12,7 +16,7 @@ export const buildSecretKeyCacheKey = ({ legacyVersion?: boolean; encryptedKey: string; }): string => { - return `sk:${orgId}:${env}:${legacyVersion ? 1 : 0}:${encryptedKey.slice(0, 16)}`; + return `sk:${orgId}:${env}:${legacyVersion ? 1 : 0}:${encryptedKey}`; }; /** Builds cache key for Stripe clients created via Autumn's master Stripe keys (env vars). */ @@ -20,12 +24,14 @@ export const buildMasterCacheKey = ({ env, accountId, legacyVersion, + secretKey, }: { env?: AppEnv; accountId?: string; legacyVersion?: boolean; + secretKey: string; }): string => { - return `master:${env || "sandbox"}:${accountId || "none"}:${legacyVersion ? 1 : 0}`; + return `master:${env || "sandbox"}:${accountId || "none"}:${legacyVersion ? 1 : 0}:${getSecretFingerprint({ secret: secretKey })}`; }; /** Builds cache key for Stripe clients created via platform (master org) flow. */ @@ -42,5 +48,5 @@ export const buildPlatformCacheKey = ({ legacyVersion?: boolean; encryptedKey: string; }): string => { - return `platform:${masterOrgId}:${env}:${accountId || "none"}:${legacyVersion ? 1 : 0}:${encryptedKey.slice(0, 16)}`; + return `platform:${masterOrgId}:${env}:${accountId || "none"}:${legacyVersion ? 1 : 0}:${encryptedKey}`; }; diff --git a/server/src/external/connect/initStripeCli.ts b/server/src/external/connect/initStripeCli.ts index aa6994463..005853644 100644 --- a/server/src/external/connect/initStripeCli.ts +++ b/server/src/external/connect/initStripeCli.ts @@ -43,6 +43,7 @@ export const initMasterStripe = (params?: { env: params?.env, accountId: params?.accountId, legacyVersion: params?.legacyVersion, + secretKey, }); return getOrCreateStripeClient({ diff --git a/server/src/initHono.ts b/server/src/initHono.ts index 6b16a3db1..3f6dcf927 100644 --- a/server/src/initHono.ts +++ b/server/src/initHono.ts @@ -16,7 +16,6 @@ import { errorMiddleware } from "./honoMiddlewares/errorMiddleware.js"; import { traceEnrichMiddleware } from "./honoMiddlewares/traceMiddleware.js"; import type { HonoEnv } from "./honoUtils/HonoEnv.js"; import { handleHealthCheck } from "./honoUtils/handleHealthCheck.js"; -import { heapSnapshotRouter } from "./internal/debug/debugRouter.js"; import { cliRouter } from "./internal/dev/cli/cliRouter.js"; import { handleOAuthCallback } from "./internal/orgs/handlers/stripeHandlers/handleOAuthCallback.js"; import { apiRouter } from "./routers/apiRouter.js"; @@ -141,7 +140,7 @@ export const createHonoApp = () => { app.route("", publicRouter); // Debug routes (no auth, dev-only guard is inside the handler) // Debug routes (auth handled internally) - app.route("/debug", heapSnapshotRouter); + // app.route("/debug", heapSnapshotRouter); // app.route("/v1/debug", debugRouter); // API Middleware diff --git a/server/src/internal/debug/debugRouter.ts b/server/src/internal/debug/debugRouter.ts index 5220b8248..38b1e71ac 100644 --- a/server/src/internal/debug/debugRouter.ts +++ b/server/src/internal/debug/debugRouter.ts @@ -1,4 +1,3 @@ -import { writeHeapSnapshot } from "node:v8"; import { sql } from "drizzle-orm"; import { Hono } from "hono"; import { dbCritical, dbGeneral } from "@/db/initDrizzle.js"; @@ -228,26 +227,23 @@ debugRouter.post("/pg-health", async (c) => { }); /** Write a V8 heap snapshot to disk. Requires secret key auth + dev-only. */ -export const heapSnapshotRouter = new Hono(); -heapSnapshotRouter.use("*", secretKeyMiddleware); -heapSnapshotRouter.use("*", orgConfigMiddleware); -heapSnapshotRouter.get("/heap-snapshot", async (c) => { - if (process.env.NODE_ENV === "production") { - return c.json({ error: "Not available in production" }, 403); - } - - const ctx = c.get("ctx"); - if (!ALLOWED_ORG_IDS.has(ctx.org?.id)) { - return c.json({ error: "Forbidden" }, 403); - } - - const snapshotDir = new URL("../../../perf/snapshots/", import.meta.url) - .pathname; - const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19); - const filename = `heap-${timestamp}-pid${process.pid}.heapsnapshot`; - const filepath = `${snapshotDir}${filename}`; - - writeHeapSnapshot(filepath); - - return c.json({ ok: true, file: filename, path: filepath }); -}); +// debugRouter.get("/heap-snapshot", async (c) => { +// if (process.env.NODE_ENV === "production") { +// return c.json({ error: "Not available in production" }, 403); +// } + +// const ctx = c.get("ctx"); +// if (!ALLOWED_ORG_IDS.has(ctx.org?.id)) { +// return c.json({ error: "Forbidden" }, 403); +// } + +// const snapshotDir = new URL("../../../perf/snapshots/", import.meta.url) +// .pathname; +// const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19); +// const filename = `heap-${timestamp}-pid${process.pid}.heapsnapshot`; +// const filepath = `${snapshotDir}${filename}`; + +// writeHeapSnapshot(filepath); + +// return c.json({ ok: true, file: filename, path: filepath }); +// }); From 60538ed9c71e9546d74c85537ab0d2a950a8f8da Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 23 Mar 2026 11:15:04 +0000 Subject: [PATCH 32/49] fix: test utilities --- .../utils/expectCustomerFeatureCorrect.ts | 3 ++- .../utils/expectCustomerProductTrialing.ts | 22 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/server/tests/integration/billing/utils/expectCustomerFeatureCorrect.ts b/server/tests/integration/billing/utils/expectCustomerFeatureCorrect.ts index 032c8a11b..46de6dada 100644 --- a/server/tests/integration/billing/utils/expectCustomerFeatureCorrect.ts +++ b/server/tests/integration/billing/utils/expectCustomerFeatureCorrect.ts @@ -4,6 +4,7 @@ import { type ApiEntityV0, ApiVersion, formatMs, + ms, } from "@autumn/shared"; import { AutumnInt } from "@/external/autumn/autumnCli"; @@ -37,7 +38,7 @@ export const expectCustomerFeatureCorrect = ({ balance, usage, resetsAt, - toleranceMs = TEN_MINUTES_MS, + toleranceMs = TEN_MINUTES_MS + ms.hours(1), }: { customerId?: string; customer?: ApiCustomerV3 | ApiEntityV0; diff --git a/server/tests/integration/billing/utils/expectCustomerProductTrialing.ts b/server/tests/integration/billing/utils/expectCustomerProductTrialing.ts index 3c95ab81f..f8156d82e 100644 --- a/server/tests/integration/billing/utils/expectCustomerProductTrialing.ts +++ b/server/tests/integration/billing/utils/expectCustomerProductTrialing.ts @@ -1,16 +1,18 @@ import { expect } from "bun:test"; -import type { - ApiCustomerV3, - ApiCustomerV5, - ApiEntityV0, - ApiEntityV2, +import { + type ApiCustomerV3, + type ApiCustomerV5, + type ApiEntityV0, + type ApiEntityV2, + ApiVersion, + formatMs, + ms, } from "@autumn/shared"; -import { ApiVersion, formatMs } from "@autumn/shared"; import { AutumnInt } from "@/external/autumn/autumnCli"; import { - expectSubscriptionTrialing, expectSubscriptionNotTrialing, expectSubscriptionPeriodAlignedWithTrialEnd, + expectSubscriptionTrialing, } from "./expect-customer-products/expectSubscriptionTrialing"; const defaultAutumn = new AutumnInt({ version: ApiVersion.V1_2 }); @@ -40,7 +42,7 @@ export const expectProductTrialing = async ({ customer: providedCustomer, productId, trialEndsAt: expectedTrialEndsAt, - toleranceMs = TEN_MINUTES_MS, + toleranceMs = TEN_MINUTES_MS + ms.hours(1), }: { customerId?: string; customer?: CustomerOrEntity; @@ -262,9 +264,7 @@ export const getTrialEndsAt = async ({ // Route to V5 if (isV5Customer(customer)) { - const sub = customer.subscriptions.find( - (s) => s.plan_id === productId, - ); + const sub = customer.subscriptions.find((s) => s.plan_id === productId); return sub?.trial_ends_at ?? null; } From c811ca8578254e939bf642c9018f186c911fb851 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 23 Mar 2026 12:11:52 +0000 Subject: [PATCH 33/49] fix: remove block against lifetime prepaid + monthly overage --- bun.lock | 18 ++ .../product-items/validateProductItems.ts | 15 -- .../auto-topup/auto-topup-edge-cases.test.ts | 158 ++++++++++++++++++ 3 files changed, 176 insertions(+), 15 deletions(-) diff --git a/bun.lock b/bun.lock index edcd28a85..666965f45 100644 --- a/bun.lock +++ b/bun.lock @@ -5416,6 +5416,8 @@ "@autumn/server/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + "@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260323.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260323.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260323.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260323.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260323.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260323.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260323.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260323.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-e8rnqL5I4DUSjiy6jiWqYFKcgKq8tC4S+uEmJwWiyTgSIGDhaRUujJ2pb6EXmi+NPeXoES6vIG7e9BsEV0WSow=="], + "@autumn/server/autumn-js": ["autumn-js@0.1.85", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4" }, "optionalPeers": ["better-auth", "better-call", "convex"] }, "sha512-PDud/t8z5bDJcD7ptyHzTaoJ0A8zkxvQ4TYcJ48RtgKDdOkVY36D1T6udVLwLDnWw4J5KXwJgEuGxHdd+cuABw=="], "@autumn/server/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="], @@ -6168,6 +6170,8 @@ "@useautumn/sdk/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + "@useautumn/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "@vercel/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], @@ -6958,6 +6962,20 @@ "@autumn/server/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + "@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260323.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3tQdgMaYn57xzRUWek+zNKMiP0z9j7fqbCjr0wlyiLNIh5jMwDArjBDKHlqSu58FKvZg7baqbqB5Mcepb3s6w=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260323.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-YE6pD4wdMqNgaBkXicQBLFwABOEmLxDclSM7grl0fw4cQSbfVwFCbSlwFDkmISKdmsgtWdYeMohrsTU710ufpg=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260323.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6zFO/SF9Gu8rtRyEt1b10TNapQGq5b/wUjCLG14675n155r4EO3JFMgnltBViV2Eatnq7G+bXD65BUBk7Ixyhw=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260323.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Jzr2gBY0ifA3XejAl7kPeHLT72JFBfzLSafOAQbANh5Iag02uPl99k8ORMfKREbYgEMMOzqPpe+r6eaKy+VEfw=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260323.1", "", { "os": "linux", "cpu": "x64" }, "sha512-UvsQdVI/LayXTowltMgtg2GHU/a/lcuOYbaAYm9+/nvgIs01VqVo0s1/lTSNBzepEk0y1EOYQ6SIsRM9lLGHxA=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260323.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-3208Xoe+3Xblf6WLVTkIdyh6401zB2eXAhu1UaDpZY0rf8SMHCxKW3TSJDytpri5UivCotZ0CNC2wgJ1TlymUA=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260323.1", "", { "os": "win32", "cpu": "x64" }, "sha512-DAiRqrcs58eXwjFOtbklbIHq70IpW7uYz1Bx3kNAzqoWlA7R4mC29N6G0kGEZDalGmj7f0HVuckq9AzaC1r6oQ=="], + "@autumn/server/autumn-js/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@autumn/server/ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], diff --git a/server/src/internal/products/product-items/validateProductItems.ts b/server/src/internal/products/product-items/validateProductItems.ts index 1380ea010..684ff6fdc 100644 --- a/server/src/internal/products/product-items/validateProductItems.ts +++ b/server/src/internal/products/product-items/validateProductItems.ts @@ -1,6 +1,5 @@ import { type AppEnv, - EntInterval, ErrCode, type Feature, FeatureType, @@ -327,20 +326,6 @@ export const validateProductItems = ({ const entInterval = itemToEntInterval({ item }); const intervalCount = item.interval_count || 1; - if (isFeaturePriceItem(item) && entInterval === EntInterval.Lifetime) { - const otherItem = newItems.find((i: any, index2: any) => { - return i.feature_id === item.feature_id && index2 !== index; - }); - - if (otherItem && isFeaturePriceItem(otherItem)) { - throw new RecaseError({ - message: `If feature is lifetime and paid, can't have any other features`, - code: ErrCode.InvalidInputs, - statusCode: StatusCodes.BAD_REQUEST, - }); - } - } - // Boolean duplicate if (isBooleanFeatureItem(item)) { const otherItem = newItems.find((i: any, index2: any) => { diff --git a/server/tests/integration/balances/auto-topup/auto-topup-edge-cases.test.ts b/server/tests/integration/balances/auto-topup/auto-topup-edge-cases.test.ts index 9d0d78a98..4353d2c90 100644 --- a/server/tests/integration/balances/auto-topup/auto-topup-edge-cases.test.ts +++ b/server/tests/integration/balances/auto-topup/auto-topup-edge-cases.test.ts @@ -1,12 +1,15 @@ import { expect, test } from "bun:test"; import type { ApiCustomerV5 } from "@autumn/shared"; +import { ResetInterval } from "@autumn/shared"; import { makeAutoTopupConfig } from "@tests/integration/balances/auto-topup/utils/makeAutoTopupConfig.js"; import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/expectCustomerInvoiceCorrect"; import { expectBalanceCorrect } from "@tests/integration/utils/expectBalanceCorrect"; +import { expectCustomerProductOptions } from "@tests/integration/utils/expectCustomerProductOptions"; import { TestFeature } from "@tests/setup/v2Features.js"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; import { timeout } from "@tests/utils/genUtils.js"; +import { advanceToNextInvoice } from "@tests/utils/testAttachUtils/testAttachUtils"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; import chalk from "chalk"; import { Decimal } from "decimal.js"; @@ -185,6 +188,161 @@ test.concurrent(`${chalk.yellowBright("auto-topup ec3: quantity < threshold — expect(afterBalance).toBe(expectedAfter); }); +test.concurrent(`${chalk.yellowBright("auto-topup ec4: pro consumable + one-off topup falls back to overage after disable")}`, async () => { + const monthlyIncludedMessages = 100; + const initialOneOffQuantity = 100; + const autoTopupThreshold = 20; + const autoTopupQuantity = 100; + const firstTrackedUsage = 185; + const secondTrackedUsage = 215; + const proBasePrice = 20; + const consumableMessagePrice = 0.1; + + const consumableMessagesItem = items.consumableMessages({ + includedUsage: monthlyIncludedMessages, + price: consumableMessagePrice, + }); + const oneOffMessagesItem = items.oneOffMessages({ + includedUsage: 0, + billingUnits: 100, + price: 10, + }); + const pro = products.pro({ + id: "topup-ec4-pro-mixed", + items: [consumableMessagesItem, oneOffMessagesItem], + }); + + const { customerId, autumnV2_1, ctx, testClockId } = await initScenario({ + customerId: "auto-topup-ec4", + setup: [ + s.customer({ paymentMethod: "success" }), + s.products({ list: [pro] }), + ], + actions: [ + s.attach({ + productId: pro.id, + options: [ + { feature_id: TestFeature.Messages, quantity: initialOneOffQuantity }, + ], + }), + ], + }); + + await autumnV2_1.customers.update(customerId, { + billing_controls: makeAutoTopupConfig({ + threshold: autoTopupThreshold, + quantity: autoTopupQuantity, + }), + }); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: firstTrackedUsage, + }); + + await timeout(AUTO_TOPUP_WAIT_MS); + + const customerAfterAutoTopup = + await autumnV2_1.customers.get(customerId); + const expectedBalanceAfterAutoTopup = new Decimal(monthlyIncludedMessages) + .add(initialOneOffQuantity) + .sub(firstTrackedUsage) + .add(autoTopupQuantity) + .toNumber(); + expectBalanceCorrect({ + customer: customerAfterAutoTopup, + featureId: TestFeature.Messages, + remaining: expectedBalanceAfterAutoTopup, + usage: firstTrackedUsage, + }); + await expectCustomerInvoiceCorrect({ + customerId, + count: 2, + latestTotal: 10, + latestStatus: "paid", + latestInvoiceProductId: pro.id, + }); + await expectCustomerProductOptions({ + ctx, + customerId, + productId: pro.id, + featureId: TestFeature.Messages, + quantity: 2, + }); + + await autumnV2_1.customers.update(customerId, { + billing_controls: makeAutoTopupConfig({ enabled: false }), + }); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: secondTrackedUsage, + }); + + const customerAfterDisable = + await autumnV2_1.customers.get(customerId); + expectBalanceCorrect({ + customer: customerAfterDisable, + featureId: TestFeature.Messages, + remaining: 0, + usage: firstTrackedUsage + secondTrackedUsage, + }); + await expectCustomerProductOptions({ + ctx, + customerId, + productId: pro.id, + featureId: TestFeature.Messages, + quantity: 2, + }); + + await advanceToNextInvoice({ + stripeCli: ctx.stripeCli, + testClockId: testClockId!, + withPause: true, + }); + + const totalUsageBeforeRenewal = new Decimal(firstTrackedUsage) + .add(secondTrackedUsage) + .toNumber(); + const totalGrantedBeforeRenewal = new Decimal(monthlyIncludedMessages) + .add(initialOneOffQuantity) + .add(autoTopupQuantity) + .toNumber(); + const expectedOverageUnits = new Decimal(totalUsageBeforeRenewal) + .sub(totalGrantedBeforeRenewal) + .toNumber(); + const expectedRenewalInvoiceTotal = new Decimal(proBasePrice) + .add(new Decimal(expectedOverageUnits).mul(consumableMessagePrice)) + .toNumber(); + + const customerAfterRenewal = + await autumnV2_1.customers.get(customerId); + + expectBalanceCorrect({ + customer: customerAfterRenewal, + featureId: TestFeature.Messages, + remaining: monthlyIncludedMessages, + // usage: 0, + breakdown: { + [ResetInterval.OneOff]: { + usage: 200, + }, + [ResetInterval.Month]: { + usage: 0, + }, + }, + }); + await expectCustomerInvoiceCorrect({ + customerId, + count: 3, + latestTotal: expectedRenewalInvoiceTotal, + latestStatus: "paid", + latestInvoiceProductId: pro.id, + }); +}); + test.concurrent(`${chalk.yellowBright("auto-topup ec5: lowered threshold respected on next trigger")}`, async () => { const oneOffItem = items.oneOffMessages({ includedUsage: 0, From 5877db54f0604ec6d99fad38d545368069b34f6f Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Mon, 23 Mar 2026 12:13:22 +0000 Subject: [PATCH 34/49] chore: rename webStandard to fetch --- apps/docs/mintlify/changelog/changelog.mdx | 6 +-- .../documentation/getting-started/setup.mdx | 53 ++++++++++--------- .../mintlify/react/hooks/autumn-handler.mdx | 4 +- bun.lock | 18 +++++++ packages/autumn-js/package.json | 14 ++--- .../adapters/{webStandard.ts => fetch.ts} | 4 +- .../autumn-js/src/backend/adapters/index.ts | 4 +- 7 files changed, 61 insertions(+), 42 deletions(-) rename packages/autumn-js/src/backend/adapters/{webStandard.ts => fetch.ts} (93%) diff --git a/apps/docs/mintlify/changelog/changelog.mdx b/apps/docs/mintlify/changelog/changelog.mdx index f75616874..248bbb31d 100644 --- a/apps/docs/mintlify/changelog/changelog.mdx +++ b/apps/docs/mintlify/changelog/changelog.mdx @@ -10,7 +10,7 @@ description: "Some new things we've shipped at Autumn HQ" The `autumn-js` SDK now ships adapters for Express, Elysia, and any framework that uses the Fetch API `Request`/`Response` objects. You can set up the `autumnHandler` in your backend with a single import — no manual request parsing required. - **Express**: Import from `autumn-js/express` and mount with `app.use()`. Requires `express.json()` before the handler - - **Elysia / Web Standard**: Import from `autumn-js/webStandard` and use with Elysia's `.mount()`, Cloudflare Workers, Deno, or any Fetch-based runtime + - **Elysia / Web Standard**: Import from `autumn-js/fetch` and use with Elysia's `.mount()`, Cloudflare Workers, Deno, or any Fetch-based runtime - **Existing adapters**: Next.js (`autumn-js/next`) and Hono (`autumn-js/hono`) continue to work as before See the [setup guide](/documentation/getting-started/setup) for framework-specific code examples. @@ -36,7 +36,7 @@ description: "Some new things we've shipped at Autumn HQ" | --- | --- | | Next.js | `autumn-js/next` | | Hono | `autumn-js/hono` | - | Elysia / Web Standard | `autumn-js/webStandard` | + | Elysia / Web Standard | `autumn-js/fetch` | | Express | `autumn-js/express` | | Other | `autumn-js/backend` | @@ -45,7 +45,7 @@ description: "Some new things we've shipped at Autumn HQ" - Express adapter for `autumnHandler` via `autumn-js/express` - [#979](https://github.com/useautumn/autumn/pull/979) - - Web Standard adapter for `autumnHandler` via `autumn-js/webStandard` (Elysia, Cloudflare Workers, Deno) - [#979](https://github.com/useautumn/autumn/pull/979) + - Web Standard adapter for `autumnHandler` via `autumn-js/fetch` (Elysia, Cloudflare Workers, Deno) - [#979](https://github.com/useautumn/autumn/pull/979) - Explicit customer creation required for `check` and `track` endpoints - [#968](https://github.com/useautumn/autumn/pull/968) - `autumn-js` SDK promoted to `1.0.0` stable release - [#968](https://github.com/useautumn/autumn/pull/968) - New Express, Elysia, and Web Standard adapters for `autumnHandler` - [#985](https://github.com/useautumn/autumn/pull/985) diff --git a/apps/docs/mintlify/documentation/getting-started/setup.mdx b/apps/docs/mintlify/documentation/getting-started/setup.mdx index 94e222f21..31836db45 100644 --- a/apps/docs/mintlify/documentation/getting-started/setup.mdx +++ b/apps/docs/mintlify/documentation/getting-started/setup.mdx @@ -124,32 +124,6 @@ app.use( ); ``` -```typescript Elysia -import { autumnHandler } from "autumn-js/webStandard"; -import { Elysia } from "elysia"; - -const app = new Elysia() - .mount( - autumnHandler({ - identify: async (request) => { - // get the user from your auth provider (example: better-auth) - const session = await auth.api.getSession({ - headers: request.headers, - }); - - return { - customerId: session?.user.id, - customerData: { - name: session?.user.name, - email: session?.user.email, - }, - }; - }, - }), - ) - .listen(3002); -``` - ```typescript Express import express from "express"; import { autumnHandler } from "autumn-js/express"; @@ -179,6 +153,33 @@ app.use( ); ``` +```typescript Web Standard (Elysia, CF Workers, etc...) +// Works with any fetch()-compatible WinterTC environment +import { autumnHandler } from "autumn-js/fetch"; +import { Elysia } from "elysia"; + +const app = new Elysia() + .mount( + autumnHandler({ + identify: async (request) => { + // get the user from your auth provider (example: better-auth) + const session = await auth.api.getSession({ + headers: request.headers, + }); + + return { + customerId: session?.user.id, + customerData: { + name: session?.user.name, + email: session?.user.email, + }, + }; + }, + }), + ) + .listen(3002); +``` + ```typescript General (framework-agnostic) // For any framework not listed above diff --git a/apps/docs/mintlify/react/hooks/autumn-handler.mdx b/apps/docs/mintlify/react/hooks/autumn-handler.mdx index bb68fc976..21abb8463 100644 --- a/apps/docs/mintlify/react/hooks/autumn-handler.mdx +++ b/apps/docs/mintlify/react/hooks/autumn-handler.mdx @@ -17,11 +17,11 @@ Import the handler from the adapter that matches your backend framework: | --- | --- | | Next.js | `autumn-js/next` | | Hono | `autumn-js/hono` | -| Elysia / Web Standard | `autumn-js/webStandard` | +| Elysia / Web Standard | `autumn-js/fetch` | | Express | `autumn-js/express` | | Other | `autumn-js/backend` | -The Web Standard adapter (`autumn-js/webStandard`) works with any runtime that uses the Fetch API `Request`/`Response` objects, including Elysia, Cloudflare Workers, and Deno. +The Web Standard adapter (`autumn-js/fetch`) works with any runtime that uses the Fetch API `Request`/`Response` objects, including Elysia, Cloudflare Workers, and Deno. The Express adapter requires `express.json()` body-parser middleware before the Autumn handler. See the [setup guide](/documentation/getting-started/setup) for full examples. diff --git a/bun.lock b/bun.lock index edcd28a85..666965f45 100644 --- a/bun.lock +++ b/bun.lock @@ -5416,6 +5416,8 @@ "@autumn/server/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + "@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260323.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260323.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260323.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260323.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260323.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260323.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260323.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260323.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-e8rnqL5I4DUSjiy6jiWqYFKcgKq8tC4S+uEmJwWiyTgSIGDhaRUujJ2pb6EXmi+NPeXoES6vIG7e9BsEV0WSow=="], + "@autumn/server/autumn-js": ["autumn-js@0.1.85", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4" }, "optionalPeers": ["better-auth", "better-call", "convex"] }, "sha512-PDud/t8z5bDJcD7ptyHzTaoJ0A8zkxvQ4TYcJ48RtgKDdOkVY36D1T6udVLwLDnWw4J5KXwJgEuGxHdd+cuABw=="], "@autumn/server/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="], @@ -6168,6 +6170,8 @@ "@useautumn/sdk/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + "@useautumn/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "@vercel/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], @@ -6958,6 +6962,20 @@ "@autumn/server/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + "@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260323.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3tQdgMaYn57xzRUWek+zNKMiP0z9j7fqbCjr0wlyiLNIh5jMwDArjBDKHlqSu58FKvZg7baqbqB5Mcepb3s6w=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260323.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-YE6pD4wdMqNgaBkXicQBLFwABOEmLxDclSM7grl0fw4cQSbfVwFCbSlwFDkmISKdmsgtWdYeMohrsTU710ufpg=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260323.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6zFO/SF9Gu8rtRyEt1b10TNapQGq5b/wUjCLG14675n155r4EO3JFMgnltBViV2Eatnq7G+bXD65BUBk7Ixyhw=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260323.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Jzr2gBY0ifA3XejAl7kPeHLT72JFBfzLSafOAQbANh5Iag02uPl99k8ORMfKREbYgEMMOzqPpe+r6eaKy+VEfw=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260323.1", "", { "os": "linux", "cpu": "x64" }, "sha512-UvsQdVI/LayXTowltMgtg2GHU/a/lcuOYbaAYm9+/nvgIs01VqVo0s1/lTSNBzepEk0y1EOYQ6SIsRM9lLGHxA=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260323.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-3208Xoe+3Xblf6WLVTkIdyh6401zB2eXAhu1UaDpZY0rf8SMHCxKW3TSJDytpri5UivCotZ0CNC2wgJ1TlymUA=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260323.1", "", { "os": "win32", "cpu": "x64" }, "sha512-DAiRqrcs58eXwjFOtbklbIHq70IpW7uYz1Bx3kNAzqoWlA7R4mC29N6G0kGEZDalGmj7f0HVuckq9AzaC1r6oQ=="], + "@autumn/server/autumn-js/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@autumn/server/ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], diff --git a/packages/autumn-js/package.json b/packages/autumn-js/package.json index c80ffbd48..822403386 100644 --- a/packages/autumn-js/package.json +++ b/packages/autumn-js/package.json @@ -1,7 +1,7 @@ { "name": "autumn-js", "description": "Autumn JS Library", - "version": "1.0.7", + "version": "1.1.0", "repository": "github:useautumn/autumn", "homepage": "https://docs.useautumn.com", "main": "./dist/sdk/index.js", @@ -38,10 +38,10 @@ "require": "./dist/backend/adapters/express.js", "import": "./dist/backend/adapters/express.mjs" }, - "./webStandard": { - "types": "./dist/backend/adapters/webStandard.d.ts", - "require": "./dist/backend/adapters/webStandard.js", - "import": "./dist/backend/adapters/webStandard.mjs" + "./fetch": { + "types": "./dist/backend/adapters/fetch.d.ts", + "require": "./dist/backend/adapters/fetch.js", + "import": "./dist/backend/adapters/fetch.mjs" }, "./hono": { "types": "./dist/backend/adapters/hono.d.ts", @@ -79,8 +79,8 @@ "express": [ "./dist/backend/adapters/express.d.ts" ], - "webStandard": [ - "./dist/backend/adapters/webStandard.d.ts" + "fetch": [ + "./dist/backend/adapters/fetch.d.ts" ] } }, diff --git a/packages/autumn-js/src/backend/adapters/webStandard.ts b/packages/autumn-js/src/backend/adapters/fetch.ts similarity index 93% rename from packages/autumn-js/src/backend/adapters/webStandard.ts rename to packages/autumn-js/src/backend/adapters/fetch.ts index 4ddb423a9..2e2bb6e8c 100644 --- a/packages/autumn-js/src/backend/adapters/webStandard.ts +++ b/packages/autumn-js/src/backend/adapters/fetch.ts @@ -1,6 +1,6 @@ import { type AuthResult, createCoreHandler } from "../core"; -export type WebStandardAutumnHandlerOptions = { +export type FetchAutumnHandlerOptions = { /** Function to identify the customer from the request */ identify: (request: Request) => AuthResult; /** Autumn API secret key */ @@ -12,7 +12,7 @@ export type WebStandardAutumnHandlerOptions = { }; export function autumnHandler( - options: WebStandardAutumnHandlerOptions, + options: FetchAutumnHandlerOptions, ): (request: Request) => Promise { const core = createCoreHandler({ identify: (raw) => options.identify(raw as Request), diff --git a/packages/autumn-js/src/backend/adapters/index.ts b/packages/autumn-js/src/backend/adapters/index.ts index c0caaba2a..38c5e9345 100644 --- a/packages/autumn-js/src/backend/adapters/index.ts +++ b/packages/autumn-js/src/backend/adapters/index.ts @@ -1,8 +1,8 @@ export type { ExpressAutumnHandlerOptions } from "./express"; export { autumnHandler as expressAutumnHandler } from "./express"; +export type { FetchAutumnHandlerOptions } from "./fetch"; +export { autumnHandler as fetchAutumnHandler } from "./fetch"; export type { HonoAutumnHandlerOptions } from "./hono"; export { autumnHandler as honoAutumnHandler } from "./hono"; export type { NextAutumnHandlerOptions } from "./next"; export { autumnHandler as nextAutumnHandler } from "./next"; -export type { WebStandardAutumnHandlerOptions } from "./webStandard"; -export { autumnHandler as webStandardAutumnHandler } from "./webStandard"; From 94d20c5acca043b31d93adb04ed1499e7a0ac5a6 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 23 Mar 2026 12:56:34 +0000 Subject: [PATCH 35/49] fix: checkout one off --- .../stripe/actionBuilders/buildStripeCheckoutSessionAction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/internal/billing/v2/providers/stripe/actionBuilders/buildStripeCheckoutSessionAction.ts b/server/src/internal/billing/v2/providers/stripe/actionBuilders/buildStripeCheckoutSessionAction.ts index 062784a1d..1b95a2934 100644 --- a/server/src/internal/billing/v2/providers/stripe/actionBuilders/buildStripeCheckoutSessionAction.ts +++ b/server/src/internal/billing/v2/providers/stripe/actionBuilders/buildStripeCheckoutSessionAction.ts @@ -44,7 +44,7 @@ export const buildStripeCheckoutSessionAction = ({ // 3. Build line_items from recurring items and one-off items const lineItems: Stripe.Checkout.SessionCreateParams.LineItem[] = [ ...recurringLineItems.filter((item) => item.quantity !== 0), - ...oneOffLineItems, + ...oneOffLineItems.filter((item) => item.quantity !== 0), ]; // 4. Trial handling (only for subscription mode) From 416a6330648a8fdfad8903a3e873742a6671f48a Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 23 Mar 2026 16:29:36 +0000 Subject: [PATCH 36/49] update: cleaned up balance webhooks --- .../mintlify/api-reference/billing/attach.mdx | 55 +- .../api-reference/billing/billingUpdate.mdx | 10 + .../api-reference/billing/multiAttach.mdx | 52 + .../api-reference/billing/previewAttach.mdx | 16 + .../billing/previewMultiAttach.mdx | 68 + .../api-reference/billing/previewUpdate.mdx | 26 + .../customers/getOrCreateCustomer.mdx | 52 + .../api-reference/customers/listCustomers.mdx | 26 + .../customers/updateCustomer.mdx | 52 + .../api-reference/entities/createEntity.mdx | 78 + .../api-reference/entities/getEntity.mdx | 26 + .../api-reference/entities/updateEntity.mdx | 52 + .../webhooks/balancesLimitReached.mdx | 22 + .../webhooks/balancesUsageAlertTriggered.mdx | 36 + .../webhooks/vercelResourcesDeleted.mdx | 20 + .../webhooks/vercelResourcesProvisioned.mdx | 28 + .../webhooks/vercelResourcesRotateSecrets.mdx | 24 + .../webhooks/vercelWebhooksEvent.mdx | 14 + apps/docs/mintlify/api/openapi.yml | 1078 +++++++++++++- apps/docs/mintlify/docs.json | 44 +- apps/docs/mintlify/documentation/webhooks.mdx | 68 +- bun.lock | 130 +- others/python-sdk/.speakeasy/gen.lock | 1284 +++++++++-------- others/python-sdk/pylintrc | 3 +- others/python-sdk/src/autumn_sdk/_version.py | 4 +- others/python-sdk/src/autumn_sdk/billing.py | 40 + .../src/autumn_sdk/errors/__init__.py | 45 +- .../src/autumn_sdk/models/__init__.py | 178 ++- .../autumn_sdk/models/aggregateeventsop.py | 6 +- .../src/autumn_sdk/models/attachop.py | 34 +- .../src/autumn_sdk/models/balance.py | 12 +- .../src/autumn_sdk/models/billingupdateop.py | 48 +- .../src/autumn_sdk/models/checkop.py | 28 +- .../src/autumn_sdk/models/createbalanceop.py | 6 +- .../src/autumn_sdk/models/createentityop.py | 149 +- .../src/autumn_sdk/models/createfeatureop.py | 8 +- .../src/autumn_sdk/models/createplanop.py | 38 +- .../autumn_sdk/models/createreferralcodeop.py | 2 +- .../src/autumn_sdk/models/customer.py | 93 +- .../src/autumn_sdk/models/customerdata.py | 70 +- .../src/autumn_sdk/models/deletebalanceop.py | 4 +- .../src/autumn_sdk/models/deletecustomerop.py | 4 +- .../src/autumn_sdk/models/deleteentityop.py | 4 +- .../src/autumn_sdk/models/deletefeatureop.py | 2 +- .../src/autumn_sdk/models/deleteplanop.py | 4 +- .../src/autumn_sdk/models/finalizelockop.py | 4 +- .../src/autumn_sdk/models/getentityop.py | 85 +- .../src/autumn_sdk/models/getfeatureop.py | 6 +- .../models/getorcreatecustomerop.py | 72 +- .../src/autumn_sdk/models/getplanop.py | 24 +- .../autumn_sdk/models/internal/__init__.py | 45 +- .../src/autumn_sdk/models/internal/globals.py | 2 +- .../src/autumn_sdk/models/listcustomersop.py | 89 +- .../src/autumn_sdk/models/listeventsop.py | 6 +- .../src/autumn_sdk/models/listfeaturesop.py | 6 +- .../src/autumn_sdk/models/listplansop.py | 24 +- .../src/autumn_sdk/models/multiattachop.py | 98 +- .../autumn_sdk/models/opencustomerportalop.py | 4 +- .../python-sdk/src/autumn_sdk/models/plan.py | 20 +- .../src/autumn_sdk/models/previewattachop.py | 70 +- .../autumn_sdk/models/previewmultiattachop.py | 134 +- .../src/autumn_sdk/models/previewupdateop.py | 84 +- .../autumn_sdk/models/redeemreferralcodeop.py | 2 +- .../src/autumn_sdk/models/setuppaymentop.py | 30 +- .../src/autumn_sdk/models/trackop.py | 8 +- .../src/autumn_sdk/models/updatebalanceop.py | 4 +- .../src/autumn_sdk/models/updatecustomerop.py | 155 +- .../src/autumn_sdk/models/updateentityop.py | 149 +- .../src/autumn_sdk/models/updatefeatureop.py | 8 +- .../src/autumn_sdk/models/updateplanop.py | 38 +- others/python-sdk/src/autumn_sdk/sdk.py | 2 +- .../src/autumn_sdk/utils/__init__.py | 44 +- .../src/autumn_sdk/utils/dynamic_imports.py | 54 - .../src/autumn_sdk/utils/eventstreaming.py | 41 +- .../src/autumn_sdk/utils/retries.py | 14 +- package.json | 2 + .../generated/getOrCreateCustomerSchemas.ts | 27 + .../src/generated/multiAttachSchemas.ts | 52 +- .../src/generated/previewAttachSchemas.ts | 4 + .../generated/previewMultiAttachSchemas.ts | 56 +- .../previewUpdateSubscriptionSchemas.ts | 18 + .../generated/updateSubscriptionSchemas.ts | 14 + packages/openapi/openapi-stripped.yml | 1077 +++++++++++++- packages/openapi/openapi.yml | 1083 +++++++++++++- packages/openapi/package.json | 2 + packages/openapi/scripts/svixPush.ts | 102 ++ .../apiReferenceGenerator/generateFields.ts | 79 +- .../utils/apiReferenceGenerator/index.ts | 114 +- .../apiReferenceGenerator/parseOpenApi.ts | 79 + .../openapi/utils/mintlifyTransform/index.ts | 83 +- packages/openapi/v2.1/openapi2.1.ts | 2 + .../openapi/v2.1/webhooks/injectWebhooks.ts | 81 ++ .../v2.1/webhooks/webhookDefinitions.ts | 1 + packages/sdk/.npmignore | 6 +- packages/sdk/.speakeasy/gen.lock | 740 ++++++---- packages/sdk/.speakeasy/out.openapi.yaml | 994 ++++++++++++- packages/sdk/.speakeasy/workflow.lock | 24 +- packages/sdk/README.md | 4 + packages/sdk/package.json | 2 +- .../sdk/src/funcs/billing-preview-update.ts | 1 + packages/sdk/src/funcs/billing-update.ts | 1 + packages/sdk/src/lib/config.ts | 4 +- packages/sdk/src/lib/files.ts | 22 - packages/sdk/src/lib/matchers.ts | 5 +- packages/sdk/src/lib/security.ts | 3 +- packages/sdk/src/models/billing-update-op.ts | 42 + packages/sdk/src/models/create-entity-op.ts | 173 ++- packages/sdk/src/models/customer-data.ts | 88 ++ packages/sdk/src/models/customer.ts | 79 + packages/sdk/src/models/get-entity-op.ts | 79 + .../src/models/get-or-create-customer-op.ts | 90 ++ packages/sdk/src/models/list-customers-op.ts | 81 ++ packages/sdk/src/models/multi-attach-op.ts | 88 ++ packages/sdk/src/models/preview-attach-op.ts | 24 + .../sdk/src/models/preview-multi-attach-op.ts | 114 ++ packages/sdk/src/models/preview-update-op.ts | 66 + packages/sdk/src/models/update-customer-op.ts | 174 +++ packages/sdk/src/models/update-entity-op.ts | 173 ++- packages/sdk/src/sdk/billing.ts | 2 + packages/sdk/tsconfig.json | 3 +- .../customers/updateCustomerData.lua | 12 +- .../external/autumn/autumnWebhookRouter.ts | 26 +- server/src/external/svix/svixHelpers.ts | 50 +- .../handlers/handleProductsUpdated.ts | 3 +- .../check/checkUtils/getV2CheckResponse.ts | 2 +- .../trackWebhooks/checkLimitReached.ts | 123 ++ .../checkUsageAlerts.ts | 54 +- .../trackWebhooks/fireTrackWebhooks.ts | 48 + .../handleThresholdReached.ts | 117 +- .../deduction/executePostgresDeduction.ts | 21 +- .../utils/deduction/executeRedisDeduction.ts | 21 +- .../sendProductsUpdated.ts | 5 +- .../updateCachedCustomerData.ts | 1 + .../updateEntityInCache.ts | 2 +- .../customers/cusUtils/initCustomer.ts | 1 + .../entities/actions/batchCreateEntities.ts | 1 + .../internal/entities/actions/updateEntity.ts | 1 + .../actions/updateEntityDbAndCache.ts | 4 +- .../apiEntityUtils/getApiEntityBase.ts | 5 +- .../entities/entityUtils/entityUtils.ts | 1 + .../entities/handlers/handleListEntities.ts | 13 +- .../limit-reached-customer.test.ts | 353 +++++ .../limit-reached-entities.test.ts | 298 ++++ .../usage-alerts/usage-alert-basic.test.ts | 118 +- .../usage-alerts/usage-alert-entities.test.ts | 464 ++++++ .../svixUsageAlertEndpoint.ts | 55 - .../attach-v1-webhooks.test.ts | 48 +- .../attach-v2-webhooks.test.ts | 48 +- .../customer-products-updated.test.ts | 48 +- .../free-to-pro-upgrade-v1-webhook.test.ts | 44 +- .../free-to-pro-upgrade-v2-webhook.test.ts | 44 +- .../update-subscription-webhooks.test.ts | 48 +- .../autumn-webhooks/utils/svixPlayClient.ts | 121 -- .../autumn-webhooks/utils/svixTestEndpoint.ts | 63 - .../customer-billing-controls.test.ts | 165 +++ .../update-entity-billing-controls.test.ts | 180 +++ .../integration/utils/svixWebhookTestUtils.ts | 214 +++ .../billingControls/entityBillingControls.ts | 4 + .../utils/check/getFeatureToUseForCheck.ts | 4 +- .../utils/convert/apiBalanceToAllowed.ts | 57 +- .../convert/apiBalanceV1ToAvailableOverage.ts | 28 +- .../webhooks/balances/balancesLimitReached.ts | 33 + .../balances/balancesUsageAlertTriggered.ts | 53 + .../api/webhooks/balancesThresholdReached.ts | 69 - shared/api/webhooks/index.ts | 6 +- shared/api/webhooks/vercel/index.ts | 4 + .../webhooks/vercel/vercelResourceDeleted.ts | 14 + .../vercel/vercelResourceProvisioned.ts | 21 + .../vercel/vercelResourceRotateSecrets.ts | 17 + .../api/webhooks/vercel/vercelWebhookEvent.ts | 10 + shared/api/webhooks/webhookEventType.ts | 12 + shared/api/webhooks/webhookRegistry.ts | 83 ++ shared/enums/WebhookEventType.ts | 5 - shared/index.ts | 3 +- .../billingControls/entityBillingControls.ts | 4 + .../cusModels/billingControls/usageAlert.ts | 10 +- .../cusModels/entityModels/entityModels.ts | 6 +- .../cusModels/entityModels/entityTable.ts | 6 +- 178 files changed, 12136 insertions(+), 2458 deletions(-) create mode 100644 apps/docs/mintlify/api-reference/webhooks/balancesLimitReached.mdx create mode 100644 apps/docs/mintlify/api-reference/webhooks/balancesUsageAlertTriggered.mdx create mode 100644 apps/docs/mintlify/api-reference/webhooks/vercelResourcesDeleted.mdx create mode 100644 apps/docs/mintlify/api-reference/webhooks/vercelResourcesProvisioned.mdx create mode 100644 apps/docs/mintlify/api-reference/webhooks/vercelResourcesRotateSecrets.mdx create mode 100644 apps/docs/mintlify/api-reference/webhooks/vercelWebhooksEvent.mdx delete mode 100644 others/python-sdk/src/autumn_sdk/utils/dynamic_imports.py create mode 100644 packages/openapi/scripts/svixPush.ts create mode 100644 packages/openapi/v2.1/webhooks/injectWebhooks.ts create mode 100644 packages/openapi/v2.1/webhooks/webhookDefinitions.ts create mode 100644 server/src/internal/balances/trackWebhooks/checkLimitReached.ts rename server/src/internal/balances/{thresholdReached => trackWebhooks}/checkUsageAlerts.ts (70%) create mode 100644 server/src/internal/balances/trackWebhooks/fireTrackWebhooks.ts rename server/src/internal/balances/{thresholdReached => trackWebhooks}/handleThresholdReached.ts (68%) create mode 100644 server/tests/integration/balances/track/limit-reached/limit-reached-customer.test.ts create mode 100644 server/tests/integration/balances/track/limit-reached/limit-reached-entities.test.ts create mode 100644 server/tests/integration/balances/track/usage-alerts/usage-alert-entities.test.ts delete mode 100644 server/tests/integration/balances/utils/usage-alert-utils/svixUsageAlertEndpoint.ts delete mode 100644 server/tests/integration/billing/autumn-webhooks/utils/svixPlayClient.ts delete mode 100644 server/tests/integration/billing/autumn-webhooks/utils/svixTestEndpoint.ts create mode 100644 server/tests/integration/utils/svixWebhookTestUtils.ts create mode 100644 shared/api/webhooks/balances/balancesLimitReached.ts create mode 100644 shared/api/webhooks/balances/balancesUsageAlertTriggered.ts delete mode 100644 shared/api/webhooks/balancesThresholdReached.ts create mode 100644 shared/api/webhooks/vercel/index.ts create mode 100644 shared/api/webhooks/vercel/vercelResourceDeleted.ts create mode 100644 shared/api/webhooks/vercel/vercelResourceProvisioned.ts create mode 100644 shared/api/webhooks/vercel/vercelResourceRotateSecrets.ts create mode 100644 shared/api/webhooks/vercel/vercelWebhookEvent.ts create mode 100644 shared/api/webhooks/webhookEventType.ts create mode 100644 shared/api/webhooks/webhookRegistry.ts delete mode 100644 shared/enums/WebhookEventType.ts diff --git a/apps/docs/mintlify/api-reference/billing/attach.mdx b/apps/docs/mintlify/api-reference/billing/attach.mdx index 849c8b492..c13aee618 100644 --- a/apps/docs/mintlify/api-reference/billing/attach.mdx +++ b/apps/docs/mintlify/api-reference/billing/attach.mdx @@ -77,15 +77,15 @@ This is useful for attaching custom metadata to the Stripe subscription created ### Body Parameters - The ID of the customer to attach the plan to. + The ID of the customer to attach the plan to. - The ID of the entity to attach the plan to. + The ID of the entity to attach the plan to. - The ID of the plan. + The ID of the plan. @@ -107,7 +107,7 @@ This is useful for attaching custom metadata to the Stripe subscription created - The version of the plan to attach. + The version of the plan to attach. @@ -278,28 +278,16 @@ This is useful for attaching custom metadata to the Stripe subscription created - - How to handle proration when updating an existing subscription. - 'prorate_immediately' charges/credits prorated amounts now, 'none' skips - creating any charges. + + How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. - - Controls when to return a checkout URL. 'always' returns a URL even if - payment succeeds, 'if_required' only when payment action is needed, 'never' - disables redirects. + + Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. - A unique ID to identify this subscription. Can be used to target specific - subscriptions in update operations when a customer has multiple products - with the same plan. + A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. @@ -317,24 +305,19 @@ This is useful for attaching custom metadata to the Stripe subscription created - URL to redirect to after successful checkout. + URL to redirect to after successful checkout. - Only applicable when the customer has an existing Stripe subscription. If - true, creates a new separate subscription instead of merging into the - existing one. + Only applicable when the customer has an existing Stripe subscription. If true, creates a new separate subscription instead of merging into the existing one. - When the plan change should take effect. 'immediate' applies now, - 'end_of_cycle' schedules for the end of the current billing cycle. By - default, upgrades are immediate and downgrades are scheduled. + When the plan change should take effect. 'immediate' applies now, 'end_of_cycle' schedules for the end of the current billing cycle. By default, upgrades are immediate and downgrades are scheduled. - Additional parameters to pass into the creation of the Stripe checkout - session. + Additional parameters to pass into the creation of the Stripe checkout session. @@ -352,8 +335,7 @@ This is useful for attaching custom metadata to the Stripe subscription created - The processor subscription ID to link. Use this to attach an existing Stripe - subscription instead of creating a new one. + The processor subscription ID to link. Use this to attach an existing Stripe subscription instead of creating a new one. @@ -384,14 +366,15 @@ This is useful for attaching custom metadata to the Stripe subscription created + ### Response - The ID of the customer. + The ID of the customer. - The ID of the entity, if the plan was attached to an entity. + The ID of the entity, if the plan was attached to an entity. @@ -421,8 +404,7 @@ This is useful for attaching custom metadata to the Stripe subscription created - URL to redirect the customer to complete payment. Null if no payment action - is required. + URL to redirect the customer to complete payment. Null if no payment action is required. @@ -439,6 +421,7 @@ This is useful for attaching custom metadata to the Stripe subscription created + ```json 200 { diff --git a/apps/docs/mintlify/api-reference/billing/billingUpdate.mdx b/apps/docs/mintlify/api-reference/billing/billingUpdate.mdx index 41209587e..4d4a633f1 100644 --- a/apps/docs/mintlify/api-reference/billing/billingUpdate.mdx +++ b/apps/docs/mintlify/api-reference/billing/billingUpdate.mdx @@ -265,6 +265,16 @@ const response = await autumn.billing.update({ If true, the subscription is updated internally without applying billing changes in Stripe. + + Controls whether balances should be recalculated during the subscription update. + + + If true, recalculates balances during the subscription update. Only applicable when updating feature quantities. + + + + + ### Response diff --git a/apps/docs/mintlify/api-reference/billing/multiAttach.mdx b/apps/docs/mintlify/api-reference/billing/multiAttach.mdx index 056698d30..8ca9a27e2 100644 --- a/apps/docs/mintlify/api-reference/billing/multiAttach.mdx +++ b/apps/docs/mintlify/api-reference/billing/multiAttach.mdx @@ -347,6 +347,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + @@ -384,6 +410,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + diff --git a/apps/docs/mintlify/api-reference/billing/previewAttach.mdx b/apps/docs/mintlify/api-reference/billing/previewAttach.mdx index 9fa53e6be..45c72fc71 100644 --- a/apps/docs/mintlify/api-reference/billing/previewAttach.mdx +++ b/apps/docs/mintlify/api-reference/billing/previewAttach.mdx @@ -796,6 +796,14 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + + When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + + + + When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + + @@ -1099,6 +1107,14 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + + When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + + + + When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + + diff --git a/apps/docs/mintlify/api-reference/billing/previewMultiAttach.mdx b/apps/docs/mintlify/api-reference/billing/previewMultiAttach.mdx index e51b1e666..378b81392 100644 --- a/apps/docs/mintlify/api-reference/billing/previewMultiAttach.mdx +++ b/apps/docs/mintlify/api-reference/billing/previewMultiAttach.mdx @@ -347,6 +347,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + @@ -384,6 +410,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + @@ -887,6 +939,14 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + + When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + + + + When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + + @@ -1190,6 +1250,14 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + + When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + + + + When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + + diff --git a/apps/docs/mintlify/api-reference/billing/previewUpdate.mdx b/apps/docs/mintlify/api-reference/billing/previewUpdate.mdx index 5f861ef42..e4c259a8d 100644 --- a/apps/docs/mintlify/api-reference/billing/previewUpdate.mdx +++ b/apps/docs/mintlify/api-reference/billing/previewUpdate.mdx @@ -231,6 +231,16 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx If true, the subscription is updated internally without applying billing changes in Stripe. + + Controls whether balances should be recalculated during the subscription update. + + + If true, recalculates balances during the subscription update. Only applicable when updating feature quantities. + + + + + ### Response @@ -728,6 +738,14 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + + When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + + + + When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + + @@ -1031,6 +1049,14 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + + When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + + + + When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + + diff --git a/apps/docs/mintlify/api-reference/customers/getOrCreateCustomer.mdx b/apps/docs/mintlify/api-reference/customers/getOrCreateCustomer.mdx index 77beac59b..23440c961 100644 --- a/apps/docs/mintlify/api-reference/customers/getOrCreateCustomer.mdx +++ b/apps/docs/mintlify/api-reference/customers/getOrCreateCustomer.mdx @@ -110,6 +110,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + @@ -217,6 +243,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + diff --git a/apps/docs/mintlify/api-reference/customers/listCustomers.mdx b/apps/docs/mintlify/api-reference/customers/listCustomers.mdx index 3a269957c..5ff98dedd 100644 --- a/apps/docs/mintlify/api-reference/customers/listCustomers.mdx +++ b/apps/docs/mintlify/api-reference/customers/listCustomers.mdx @@ -138,6 +138,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + diff --git a/apps/docs/mintlify/api-reference/customers/updateCustomer.mdx b/apps/docs/mintlify/api-reference/customers/updateCustomer.mdx index d58f82e54..da1b6ce1c 100644 --- a/apps/docs/mintlify/api-reference/customers/updateCustomer.mdx +++ b/apps/docs/mintlify/api-reference/customers/updateCustomer.mdx @@ -98,6 +98,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + @@ -205,6 +231,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + diff --git a/apps/docs/mintlify/api-reference/entities/createEntity.mdx b/apps/docs/mintlify/api-reference/entities/createEntity.mdx index e382339cc..8a1af6506 100644 --- a/apps/docs/mintlify/api-reference/entities/createEntity.mdx +++ b/apps/docs/mintlify/api-reference/entities/createEntity.mdx @@ -38,6 +38,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + @@ -137,6 +163,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + @@ -1101,6 +1153,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + diff --git a/apps/docs/mintlify/api-reference/entities/getEntity.mdx b/apps/docs/mintlify/api-reference/entities/getEntity.mdx index 27a9912ca..e3385c575 100644 --- a/apps/docs/mintlify/api-reference/entities/getEntity.mdx +++ b/apps/docs/mintlify/api-reference/entities/getEntity.mdx @@ -967,6 +967,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + diff --git a/apps/docs/mintlify/api-reference/entities/updateEntity.mdx b/apps/docs/mintlify/api-reference/entities/updateEntity.mdx index 6c26b0f9b..2e72a9299 100644 --- a/apps/docs/mintlify/api-reference/entities/updateEntity.mdx +++ b/apps/docs/mintlify/api-reference/entities/updateEntity.mdx @@ -38,6 +38,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + @@ -991,6 +1017,32 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + List of usage alert configurations per feature. + + + The feature ID this alert applies to. If omitted, the alert applies globally. + + + + Whether this usage alert is enabled. + + + + The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + + + + Whether the threshold is an absolute usage count or a percentage of the usage allowance. + + + + Optional user-defined label to distinguish multiple alerts on the same feature. + + + + + diff --git a/apps/docs/mintlify/api-reference/webhooks/balancesLimitReached.mdx b/apps/docs/mintlify/api-reference/webhooks/balancesLimitReached.mdx new file mode 100644 index 000000000..781e60296 --- /dev/null +++ b/apps/docs/mintlify/api-reference/webhooks/balancesLimitReached.mdx @@ -0,0 +1,22 @@ +--- +title: "Limit Reached" +openapi: "api/openapi.yml webhook balances.limit_reached" +--- + +### Payload Fields + + + The ID of the customer who hit the limit. + + + + The entity ID, if the limit was reached on a specific entity. + + + + The feature ID whose limit was reached. + + + + Which limit was hit: included allowance, max purchase cap, or spend limit. + diff --git a/apps/docs/mintlify/api-reference/webhooks/balancesUsageAlertTriggered.mdx b/apps/docs/mintlify/api-reference/webhooks/balancesUsageAlertTriggered.mdx new file mode 100644 index 000000000..a1852e162 --- /dev/null +++ b/apps/docs/mintlify/api-reference/webhooks/balancesUsageAlertTriggered.mdx @@ -0,0 +1,36 @@ +--- +title: "Usage Alert Triggered" +openapi: "api/openapi.yml webhook balances.usage_alert_triggered" +--- + +### Payload Fields + + + The ID of the customer whose usage alert was triggered. + + + + The feature ID the alert applies to. + + + + The entity ID the alert applies to, if the usage was entity-scoped. + + + + Details of the usage alert that was triggered. + + + User-defined label for the alert, if provided. + + + + The threshold value that was crossed. + + + + Whether the threshold is an absolute usage count or a percentage. + + + + diff --git a/apps/docs/mintlify/api-reference/webhooks/vercelResourcesDeleted.mdx b/apps/docs/mintlify/api-reference/webhooks/vercelResourcesDeleted.mdx new file mode 100644 index 000000000..3640f088a --- /dev/null +++ b/apps/docs/mintlify/api-reference/webhooks/vercelResourcesDeleted.mdx @@ -0,0 +1,20 @@ +--- +title: "Resource Deleted" +openapi: "api/openapi.yml webhook vercel.resources.deleted" +--- + +### Payload Fields + + + The resource that was deleted. + + + The unique identifier of the deleted resource. + + + + + + + The Vercel integration configuration ID. + diff --git a/apps/docs/mintlify/api-reference/webhooks/vercelResourcesProvisioned.mdx b/apps/docs/mintlify/api-reference/webhooks/vercelResourcesProvisioned.mdx new file mode 100644 index 000000000..9add61c97 --- /dev/null +++ b/apps/docs/mintlify/api-reference/webhooks/vercelResourcesProvisioned.mdx @@ -0,0 +1,28 @@ +--- +title: "Resource Provisioned" +openapi: "api/openapi.yml webhook vercel.resources.provisioned" +--- + +### Payload Fields + + + The resource that was provisioned. + + + The unique identifier of the provisioned resource. + + + + The display name of the provisioned resource. + + + + + + + The Vercel integration configuration ID. + + + + An access token that can be used to patch the resource's secrets. + diff --git a/apps/docs/mintlify/api-reference/webhooks/vercelResourcesRotateSecrets.mdx b/apps/docs/mintlify/api-reference/webhooks/vercelResourcesRotateSecrets.mdx new file mode 100644 index 000000000..74f8e58c6 --- /dev/null +++ b/apps/docs/mintlify/api-reference/webhooks/vercelResourcesRotateSecrets.mdx @@ -0,0 +1,24 @@ +--- +title: "Rotate Secrets" +openapi: "api/openapi.yml webhook vercel.resources.rotate_secrets" +--- + +### Payload Fields + + + The resource whose secrets should be rotated. + + + The unique identifier of the resource. + + + + + + + The Vercel integration configuration ID. + + + + The raw request body from Vercel's rotation request. + diff --git a/apps/docs/mintlify/api-reference/webhooks/vercelWebhooksEvent.mdx b/apps/docs/mintlify/api-reference/webhooks/vercelWebhooksEvent.mdx new file mode 100644 index 000000000..1b08b751f --- /dev/null +++ b/apps/docs/mintlify/api-reference/webhooks/vercelWebhooksEvent.mdx @@ -0,0 +1,14 @@ +--- +title: "Webhook Event" +openapi: "api/openapi.yml webhook vercel.webhooks.event" +--- + +### Payload Fields + + + The Vercel integration configuration ID. + + + + The raw Vercel webhook event payload. + diff --git a/apps/docs/mintlify/api/openapi.yml b/apps/docs/mintlify/api/openapi.yml index c907f5ddf..16dae50d0 100644 --- a/apps/docs/mintlify/api/openapi.yml +++ b/apps/docs/mintlify/api/openapi.yml @@ -86,6 +86,7 @@ components: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -122,6 +123,40 @@ components: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a percentage + (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) title: CustomerData description: Customer details to set when creating a customer @@ -161,6 +196,7 @@ components: enum: - sandbox - live + type: string description: The environment this customer was created in. metadata: type: object @@ -203,6 +239,7 @@ components: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -239,6 +276,40 @@ components: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a percentage + (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) subscriptions: type: array @@ -266,6 +337,7 @@ components: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -393,6 +465,7 @@ components: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -530,6 +603,7 @@ components: enum: - sandbox - live + type: string description: The environment (sandbox/live) required: - id @@ -575,6 +649,7 @@ components: - fixed_discount - free_product - invoice_credits + type: string description: The type of reward discount_value: type: number @@ -584,6 +659,7 @@ components: - one_off - months - forever + type: string description: How long the discount lasts duration_value: anyOf: @@ -782,6 +858,7 @@ components: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -831,6 +908,7 @@ components: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -895,6 +973,7 @@ components: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. @@ -928,6 +1007,7 @@ components: enum: - graduated - volume + type: string interval: enum: - one_off @@ -936,6 +1016,7 @@ components: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -950,6 +1031,7 @@ components: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." max_purchase: @@ -992,6 +1074,7 @@ components: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -1020,6 +1103,7 @@ components: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -1038,6 +1122,7 @@ components: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -1060,6 +1145,7 @@ components: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -1076,6 +1162,7 @@ components: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -1115,6 +1202,7 @@ components: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -1244,6 +1332,7 @@ components: - quarter - semi_annual - year + type: string - const: multiple description: The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals. @@ -1278,6 +1367,7 @@ components: enum: - graduated - volume + type: string description: "How tiers are applied: graduated (split across bands) or volume (flat rate for the matched tier)." billing_units: @@ -1287,6 +1377,7 @@ components: enum: - prepaid - usage_based + type: string description: Whether usage is prepaid or billed pay-per-use. max_purchase: anyOf: @@ -1463,6 +1554,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -1499,6 +1591,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) expand: type: array @@ -1602,6 +1728,7 @@ paths: enum: - active - scheduled + type: string description: Filter by customer product status. Defaults to active and scheduled search: type: string @@ -1659,6 +1786,7 @@ paths: enum: - sandbox - live + type: string description: The environment this customer was created in. metadata: type: object @@ -1701,6 +1829,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -1737,6 +1866,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this + is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) subscriptions: type: array @@ -1764,6 +1927,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -1892,6 +2056,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -2221,6 +2386,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -2257,6 +2423,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) new_customer_id: $ref: "#/components/schemas/CustomerId" @@ -2311,6 +2511,7 @@ paths: enum: - sandbox - live + type: string description: The environment this customer was created in. metadata: type: object @@ -2353,6 +2554,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -2389,6 +2591,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) subscriptions: type: array @@ -2416,6 +2652,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -2544,6 +2781,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -2853,6 +3091,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -2891,6 +3130,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -2927,6 +3167,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -2935,6 +3176,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -2950,6 +3192,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -2969,6 +3212,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -2977,6 +3221,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -2993,6 +3238,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -3019,6 +3265,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -3125,6 +3372,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -3174,6 +3422,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -3238,6 +3487,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. @@ -3271,6 +3521,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -3279,6 +3530,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -3294,6 +3546,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." @@ -3337,6 +3590,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -3365,6 +3619,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -3383,6 +3638,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -3405,6 +3661,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -3421,6 +3678,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -3629,6 +3887,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -3678,6 +3937,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -3742,6 +4002,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. @@ -3775,6 +4036,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -3783,6 +4045,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -3798,6 +4061,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." @@ -3841,6 +4105,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -3869,6 +4134,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -3887,6 +4153,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -3909,6 +4176,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -3925,6 +4193,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -4112,6 +4381,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -4161,6 +4431,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -4225,6 +4496,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units @@ -4259,6 +4531,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -4267,6 +4540,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -4282,6 +4556,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." @@ -4326,6 +4601,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -4355,6 +4631,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -4373,6 +4650,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -4395,6 +4673,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -4411,6 +4690,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -4567,6 +4847,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -4607,6 +4888,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -4643,6 +4925,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -4651,6 +4934,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -4666,6 +4950,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -4685,6 +4970,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -4693,6 +4979,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -4709,6 +4996,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -4736,6 +5024,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -4827,6 +5116,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -4876,6 +5166,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -4940,6 +5231,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. @@ -4973,6 +5265,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -4981,6 +5274,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -4996,6 +5290,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." @@ -5039,6 +5334,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -5067,6 +5363,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -5085,6 +5382,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -5107,6 +5405,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -5123,6 +5422,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -5338,6 +5638,7 @@ paths: - boolean - metered - credit_system + type: string description: The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. @@ -5423,6 +5724,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -5570,6 +5872,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -5699,6 +6002,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -5833,6 +6137,7 @@ paths: - boolean - metered - credit_system + type: string description: The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. @@ -5918,6 +6223,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -6167,6 +6473,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -6208,6 +6515,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -6244,6 +6552,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -6252,6 +6561,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -6267,6 +6577,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -6286,6 +6597,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -6294,6 +6606,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -6310,6 +6623,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -6336,6 +6650,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -6379,6 +6694,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -6387,6 +6703,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -6423,6 +6740,7 @@ paths: enum: - immediate - end_of_cycle + type: string description: When the plan change should take effect. 'immediate' applies now, 'end_of_cycle' schedules for the end of the current billing cycle. By default, upgrades are immediate and downgrades are @@ -6554,6 +6872,7 @@ paths: - 3ds_required - payment_method_required - payment_failed + type: string description: The type of action required to complete the payment. reason: type: string @@ -6657,6 +6976,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -6698,6 +7018,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -6734,6 +7055,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -6742,6 +7064,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -6758,6 +7081,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -6777,6 +7101,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -6785,6 +7110,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -6801,6 +7127,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -6859,6 +7186,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -6925,6 +7253,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -6965,6 +7294,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is + a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. required: - feature_id @@ -7041,6 +7404,7 @@ paths: - 3ds_required - payment_method_required - payment_failed + type: string description: The type of action required to complete the payment. reason: type: string @@ -7195,6 +7559,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -7236,6 +7601,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -7272,6 +7638,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -7280,6 +7647,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -7295,6 +7663,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -7314,6 +7683,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -7322,6 +7692,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -7338,6 +7709,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -7364,6 +7736,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -7407,6 +7780,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -7415,6 +7789,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -7451,6 +7826,7 @@ paths: enum: - immediate - end_of_cycle + type: string description: When the plan change should take effect. 'immediate' applies now, 'end_of_cycle' schedules for the end of the current billing cycle. By default, upgrades are immediate and downgrades are @@ -7776,10 +8152,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. - required: - - plan_id + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. + required: + - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being added or updated. outgoing: type: array @@ -7813,10 +8203,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being removed or ended. required: - customer_id @@ -7924,6 +8328,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -7965,6 +8370,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -8001,6 +8407,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -8009,6 +8416,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -8025,6 +8433,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -8044,6 +8453,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -8052,6 +8462,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -8068,6 +8479,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -8126,6 +8538,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -8192,6 +8605,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -8232,6 +8646,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is + a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. required: - feature_id @@ -8502,10 +8950,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being added or updated. outgoing: type: array @@ -8539,10 +9001,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being removed or ended. required: - customer_id @@ -8697,6 +9173,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -8738,6 +9215,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -8774,6 +9252,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -8782,6 +9261,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -8797,6 +9277,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -8816,6 +9297,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -8824,6 +9306,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -8840,6 +9323,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -8866,6 +9350,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -8909,6 +9394,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -8917,6 +9403,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -8931,6 +9418,7 @@ paths: - cancel_immediately - cancel_end_of_cycle - uncancel + type: string description: Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. @@ -8938,6 +9426,17 @@ paths: type: boolean description: If true, the subscription is updated internally without applying billing changes in Stripe. + recalculate_balances: + type: object + properties: + enabled: + type: boolean + description: If true, recalculates balances during the subscription update. Only + applicable when updating feature quantities. + required: + - enabled + description: Controls whether balances should be recalculated during the + subscription update. required: - customer_id title: UpdateSubscriptionParams @@ -9008,6 +9507,7 @@ paths: - 3ds_required - payment_method_required - payment_failed + type: string description: The type of action required to complete the payment. reason: type: string @@ -9149,6 +9649,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -9190,6 +9691,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -9226,6 +9728,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -9234,6 +9737,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -9249,6 +9753,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -9268,6 +9773,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -9276,6 +9782,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -9292,6 +9799,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -9318,6 +9826,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -9361,6 +9870,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -9369,6 +9879,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -9383,6 +9894,7 @@ paths: - cancel_immediately - cancel_end_of_cycle - uncancel + type: string description: Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. @@ -9390,6 +9902,17 @@ paths: type: boolean description: If true, the subscription is updated internally without applying billing changes in Stripe. + recalculate_balances: + type: object + properties: + enabled: + type: boolean + description: If true, recalculates balances during the subscription update. Only + applicable when updating feature quantities. + required: + - enabled + description: Controls whether balances should be recalculated during the + subscription update. required: - customer_id title: PreviewUpdateParams @@ -9654,10 +10177,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being added or updated. outgoing: type: array @@ -9691,10 +10228,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being removed or ended. intent: enum: @@ -9704,6 +10255,7 @@ paths: - cancel_end_of_cycle - uncancel - none + type: string required: - customer_id - line_items @@ -9926,6 +10478,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -9967,6 +10520,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -10003,6 +10557,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -10011,6 +10566,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -10026,6 +10582,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -10045,6 +10602,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -10053,6 +10611,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -10069,6 +10628,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -10095,6 +10655,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -10115,6 +10676,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -10312,6 +10874,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the balance resets (e.g., 'month', 'day', 'year'). interval_count: @@ -10439,6 +11002,7 @@ paths: - quarter - semi_annual - year + type: string description: Target a specific balance by its reset interval. Use when the customer has multiple balances for the same feature with different reset intervals. @@ -10548,6 +11112,7 @@ paths: - quarter - semi_annual - year + type: string description: Target a specific balance by its reset interval. Use when the customer has multiple balances for the same feature with different reset intervals. @@ -10624,6 +11189,7 @@ paths: enum: - confirm - release + type: string description: Use 'confirm' to commit the deduction, or 'release' to return the held balance. override_value: @@ -10838,6 +11404,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -10912,6 +11479,7 @@ paths: enum: - usage_limit - feature_flag + type: string description: The reason access was denied. 'usage_limit' means the customer exceeded their balance, 'feature_flag' means the feature is not included in their plan. @@ -10947,6 +11515,7 @@ paths: enum: - sandbox - live + type: string description: The environment of the product is_add_on: type: boolean @@ -10976,6 +11545,7 @@ paths: - feature - priced_feature - price + type: string - type: "null" description: The type of the product item feature_id: @@ -10991,6 +11561,7 @@ paths: - continuous_use - boolean - static + type: string - type: "null" description: Single use features are used once and then depleted, like API calls or credits. Continuous use features are @@ -11014,6 +11585,7 @@ paths: - quarter - semi_annual - year + type: string - type: "null" description: The reset or billing interval of the product item. If null, feature will have no reset date, and if there's a @@ -11044,6 +11616,7 @@ paths: - enum: - graduated - volume + type: string - type: "null" description: "How tiers are applied: graduated (split across bands) or volume (flat rate for the matched tier). Defaults @@ -11053,6 +11626,7 @@ paths: - enum: - prepaid - pay_per_use + type: string - type: "null" description: Whether the feature should be prepaid upfront or billed for how much they use end of billing period. @@ -11113,6 +11687,7 @@ paths: enum: - month - forever + type: string default: month length: type: number @@ -11127,6 +11702,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string - type: "null" on_decrease: anyOf: @@ -11136,6 +11712,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string - type: "null" - type: "null" description: Configuration for rollover and proration behavior of the feature. @@ -11151,6 +11728,7 @@ paths: - day - month - year + type: string description: The duration type of the free trial length: type: number @@ -11195,6 +11773,7 @@ paths: - cancel - expired - past_due + type: string description: Scenario for when this product is used in attach flows properties: type: object @@ -11701,6 +12280,7 @@ paths: - last_cycle - 1bc - 3bc + type: string description: Time range to aggregate events for. Either range or custom_range must be provided bin_size: @@ -11708,6 +12288,7 @@ paths: - day - hour - month + type: string default: day description: Size of the time bins to aggregate events for. Defaults to hour if range is 24h, otherwise day @@ -11930,6 +12511,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. customer_data: $ref: "#/components/schemas/CustomerData" @@ -11988,6 +12603,7 @@ paths: enum: - sandbox - live + type: string description: The environment (sandbox/live) subscriptions: type: array @@ -12015,6 +12631,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -12138,6 +12755,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -12223,6 +12841,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. invoices: type: array @@ -12422,6 +13074,7 @@ paths: enum: - sandbox - live + type: string description: The environment (sandbox/live) subscriptions: type: array @@ -12449,6 +13102,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -12572,6 +13226,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -12657,6 +13312,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. invoices: type: array @@ -12824,6 +13513,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls to replace on the entity. required: - entity_id @@ -12873,6 +13596,7 @@ paths: enum: - sandbox - live + type: string description: The environment (sandbox/live) subscriptions: type: array @@ -12900,6 +13624,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -13023,6 +13748,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -13108,6 +13834,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. invoices: type: array @@ -13518,3 +14278,317 @@ x-speakeasy-globals: type: string default: 2.2.0 x-speakeasy-globals-hidden: true +webhooks: + balances.usage_alert_triggered: + post: + operationId: balancesUsageAlertTriggered + summary: Usage Alert Triggered + description: Fired when a customer crosses a configured usage alert threshold. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: balances.usage_alert_triggered + description: The webhook event type. + data: + examples: + - customer_id: org_123 + feature_id: api_calls + entity_id: workspace_abc + usage_alert: + name: 80% usage warning + threshold: 80 + threshold_type: usage_percentage_threshold + type: object + properties: + customer_id: + description: The ID of the customer whose usage alert was triggered. + type: string + feature_id: + description: The feature ID the alert applies to. + type: string + entity_id: + description: The entity ID the alert applies to, if the usage was entity-scoped. + type: string + usage_alert: + description: Details of the usage alert that was triggered. + type: object + properties: + name: + description: User-defined label for the alert, if provided. + type: string + threshold: + description: The threshold value that was crossed. + type: number + threshold_type: + description: Whether the threshold is an absolute usage count or a percentage. + type: string + enum: + - usage + - usage_percentage + required: + - threshold + - threshold_type + additionalProperties: false + required: + - customer_id + - feature_id + - usage_alert + additionalProperties: false + example: + type: balances.usage_alert_triggered + data: + customer_id: org_123 + feature_id: api_calls + entity_id: workspace_abc + usage_alert: + name: 80% usage warning + threshold: 80 + threshold_type: usage_percentage_threshold + responses: + "200": + description: Webhook received successfully. + balances.limit_reached: + post: + operationId: balancesLimitReached + summary: Limit Reached + description: Fired when a customer reaches the limit for a feature (included + allowance, max purchase, or spend limit). + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: balances.limit_reached + description: The webhook event type. + data: + examples: + - customer_id: org_123 + entity_id: workspace_abc + feature_id: api_calls + limit_type: included + type: object + properties: + customer_id: + description: The ID of the customer who hit the limit. + type: string + entity_id: + description: The entity ID, if the limit was reached on a specific entity. + type: string + feature_id: + description: The feature ID whose limit was reached. + type: string + limit_type: + description: "Which limit was hit: included allowance, max purchase cap, or + spend limit." + type: string + enum: + - included + - max_purchase + - spend_limit + required: + - customer_id + - feature_id + - limit_type + additionalProperties: false + example: + type: balances.limit_reached + data: + customer_id: org_123 + entity_id: workspace_abc + feature_id: api_calls + limit_type: included + responses: + "200": + description: Webhook received successfully. + vercel.resources.deleted: + post: + operationId: vercelResourcesDeleted + summary: Resource Deleted + description: When a Vercel resource is deleted, you'll need to handle + de-provisioning any API keys or other non-Autumn controlled data for + this user. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.resources.deleted + description: The webhook event type. + data: + type: object + properties: + resource: + description: The resource that was deleted. + type: object + properties: + id: + description: The unique identifier of the deleted resource. + type: string + required: + - id + additionalProperties: false + installation_id: + description: The Vercel integration configuration ID. + type: string + required: + - resource + - installation_id + additionalProperties: false + responses: + "200": + description: Webhook received successfully. + vercel.resources.provisioned: + post: + operationId: vercelResourcesProvisioned + summary: Resource Provisioned + description: When a Vercel resource is created, you'll need to provision a + secret key for your service. Then you can use the provided access token + to patch the resource's secrets. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.resources.provisioned + description: The webhook event type. + data: + type: object + properties: + resource: + description: The resource that was provisioned. + type: object + properties: + id: + description: The unique identifier of the provisioned resource. + type: string + name: + description: The display name of the provisioned resource. + type: string + required: + - id + - name + additionalProperties: false + installation_id: + description: The Vercel integration configuration ID. + type: string + access_token: + description: An access token that can be used to patch the resource's secrets. + type: string + required: + - resource + - installation_id + - access_token + additionalProperties: false + responses: + "200": + description: Webhook received successfully. + vercel.resources.rotate_secrets: + post: + operationId: vercelResourcesRotateSecrets + summary: Rotate Secrets + description: This event is sent when Vercel requires a resource's secrets to be + rotated. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.resources.rotate_secrets + description: The webhook event type. + data: + type: object + properties: + resource: + description: The resource whose secrets should be rotated. + type: object + properties: + id: + description: The unique identifier of the resource. + type: string + required: + - id + additionalProperties: false + installation_id: + description: The Vercel integration configuration ID. + type: string + vercel_request_body: + description: The raw request body from Vercel's rotation request. + required: + - resource + - installation_id + - vercel_request_body + additionalProperties: false + responses: + "200": + description: Webhook received successfully. + vercel.webhooks.event: + post: + operationId: vercelWebhooksEvent + summary: Webhook Event + description: Passthrough webhook for Vercel events. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.webhooks.event + description: The webhook event type. + data: + type: object + properties: + installation_id: + description: The Vercel integration configuration ID. + type: string + event: + description: The raw Vercel webhook event payload. + required: + - installation_id + - event + additionalProperties: false + responses: + "200": + description: Webhook received successfully. diff --git a/apps/docs/mintlify/docs.json b/apps/docs/mintlify/docs.json index 6ad9cc794..e6120fe64 100644 --- a/apps/docs/mintlify/docs.json +++ b/apps/docs/mintlify/docs.json @@ -12,7 +12,11 @@ "strict": false }, "contextual": { - "options": ["copy", "cursor", "view"] + "options": [ + "copy", + "cursor", + "view" + ] }, "banner": { "content": "You're viewing the docs for Autumn's v2 API. You can find the previous API version [here](https://docs-v1.useautumn.com).", @@ -167,7 +171,11 @@ { "tab": "CLI", "icon": "square-terminal", - "pages": ["cli/getting-started", "cli/config", "cli/commands"] + "pages": [ + "cli/getting-started", + "cli/config", + "cli/commands" + ] }, { "tab": "API Reference", @@ -242,7 +250,6 @@ "api-reference/features/deleteFeature" ] }, - { "group": "Referrals", "pages": [ @@ -250,6 +257,27 @@ "api-reference/referrals/redeemReferralCode" ] }, + { + "group": "Webhook Events", + "pages": [ + { + "group": "Balances", + "pages": [ + "api-reference/webhooks/balancesUsageAlertTriggered", + "api-reference/webhooks/balancesLimitReached" + ] + }, + { + "group": "Vercel", + "pages": [ + "api-reference/webhooks/vercelResourcesDeleted", + "api-reference/webhooks/vercelResourcesProvisioned", + "api-reference/webhooks/vercelResourcesRotateSecrets", + "api-reference/webhooks/vercelWebhooksEvent" + ] + } + ] + }, { "group": "Platform (Beta)", "pages": [ @@ -266,7 +294,9 @@ { "tab": "Changelog", "icon": "list-ol", - "pages": ["changelog/changelog"] + "pages": [ + "changelog/changelog" + ] } ] }, @@ -279,7 +309,11 @@ "display": "interactive" }, "examples": { - "languages": ["typescript", "python", "bash"] + "languages": [ + "typescript", + "python", + "bash" + ] } }, "background": { diff --git a/apps/docs/mintlify/documentation/webhooks.mdx b/apps/docs/mintlify/documentation/webhooks.mdx index c87fa07b3..8adf4abcd 100644 --- a/apps/docs/mintlify/documentation/webhooks.mdx +++ b/apps/docs/mintlify/documentation/webhooks.mdx @@ -60,36 +60,64 @@ Fired when a customer's product or plan changes. The `scenario` field indicates } ``` -### customer.threshold_reached +### balances.limit_reached -Fired when a customer reaches a usage threshold for a feature. This is useful for notifying users before they hit hard limits. +Fired when a customer hits a usage limit for a feature. A limit can be the included allowance, a max purchase cap, or a spend limit. -| Threshold Type | Description | -|----------------|-------------| -| `limit_reached` | Customer has reached their usage limit | -| `allowance_used` | Customer has exhausted their allowance | +| Limit Type | Description | +|------------|-------------| +| `included` | Customer has exhausted their included allowance | +| `max_purchase` | Customer has reached the maximum purchase cap for overage | +| `spend_limit` | Customer has hit their configured spend limit | -In most cases, these events will fire at the same time, as the included allowance is the same as the user's usage limit. However, you may have a feature with a different included allowance and a different "max purchase" limit. +**Example payload:** + +```json expandable +{ + "type": "balances.limit_reached", + "data": { + "customer_id": "user_123", + "feature_id": "api_calls", + "limit_type": "included" + } +} +``` -Eg: 100 API calls included, then 0.1 per API call, with a maximum purchase limit of 1000 API calls. +For entity-scoped usage, the payload will also include an `entity_id`: + +```json expandable +{ + "type": "balances.limit_reached", + "data": { + "customer_id": "user_123", + "feature_id": "api_calls", + "entity_id": "team_456", + "limit_type": "max_purchase" + } +} +``` + +### balances.usage_alert_triggered + +Fired when a customer crosses a configured usage alert threshold. Usage alerts let you monitor when customers approach or exceed specific usage levels for a feature. + +| Alert Threshold Type | Description | +|---------------------|-------------| +| `usage` | An absolute usage count was crossed | +| `usage_percentage` | A percentage of the usage allowance was crossed | **Example payload:** ```json expandable { - "type": "customer.threshold_reached", + "type": "balances.usage_alert_triggered", "data": { - "threshold_type": "limit_reached", - "customer": { - "id": "user_123", - "name": "John Doe", - "email": "john@example.com", - "balances": { ... } - }, - "feature": { - "id": "api_calls", - "name": "API Calls", - "type": "metered" + "customer_id": "user_123", + "feature_id": "api_calls", + "usage_alert": { + "name": "80% usage warning", + "threshold": 80, + "threshold_type": "usage_percentage" } } } diff --git a/bun.lock b/bun.lock index edcd28a85..2c6fccad6 100644 --- a/bun.lock +++ b/bun.lock @@ -224,6 +224,8 @@ "@orpc/contract": "catalog:", "@orpc/openapi": "^1.13.4", "@orpc/zod": "^1.13.4", + "dotenv": "^17.3.1", + "svix": "^1.89.0", "yaml": "^2.8.1", "zod-openapi": "^5.4.1", }, @@ -246,7 +248,7 @@ "@eslint/js": "^9.26.0", "eslint": "^9.26.0", "globals": "^15.14.0", - "tshy": "^3.3.2", + "tshy": "^2.0.0", "typescript": "~5.8.3", "typescript-eslint": "^8.26.0", }, @@ -3266,7 +3268,7 @@ "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], - "foreground-child": ["foreground-child@4.0.3", "", { "dependencies": { "signal-exit": "^4.0.1" } }, "sha512-yeXZaNbCBGaT9giTpLPBdtedzjwhlJBUoL/R4BVQU5mn0TQXOHwVIl1Q2DMuBIdNno4ktA1abZ7dQFVxD6uHxw=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], @@ -3764,8 +3766,6 @@ "jsonc-parser": ["jsonc-parser@2.2.1", "", {}, "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w=="], - "jsonc-simple-parser": ["jsonc-simple-parser@3.0.0", "", { "dependencies": { "reghex": "^3.0.2" } }, "sha512-0qi9Kuj4JPar4/3b9wZteuPZrTeFzXsQyOZj7hksnReCZN3Vr17Doz7w/i3E9XH7vRkVTHhHES+r1h97I+hfww=="], - "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], "jsonpath-plus": ["jsonpath-plus@10.4.0", "", { "dependencies": { "@jsep-plugin/assignment": "^1.3.0", "@jsep-plugin/regex": "^1.0.4", "jsep": "^1.4.0" }, "bin": { "jsonpath": "bin/jsonpath-cli.js", "jsonpath-plus": "bin/jsonpath-cli.js" } }, "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA=="], @@ -4610,8 +4610,6 @@ "regexpp": ["regexpp@3.2.0", "", {}, "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg=="], - "reghex": ["reghex@3.0.2", "", {}, "sha512-Zb9DJ5u6GhgqRSBnxV2QSnLqEwcKxHWFA1N2yUa4ZUAO1P8jlWKYtWZ6/ooV6yylspGXJX0O/uNzEv0xrCtwaA=="], - "rehype-harden": ["rehype-harden@1.1.8", "", { "dependencies": { "unist-util-visit": "^5.0.0" } }, "sha512-Qn7vR1xrf6fZCrkm9TDWi/AB4ylrHy+jqsNm1EHOAmbARYA6gsnVJBq/sdBh6kmT4NEZxH5vgIjrscefJAOXcw=="], "rehype-katex": ["rehype-katex@7.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/katex": "^0.16.0", "hast-util-from-html-isomorphic": "^2.0.0", "hast-util-to-text": "^4.0.0", "katex": "^0.16.0", "unist-util-visit-parents": "^6.0.0", "vfile": "^6.0.0" } }, "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA=="], @@ -4678,7 +4676,7 @@ "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], - "resolve-import": ["resolve-import@2.4.0", "", { "dependencies": { "glob": "^13.0.0", "walk-up-path": "^4.0.0" } }, "sha512-gLWKdA5tiv5j/D7ipR47u3ovbVfzFPrctTdw2Ulnpmr6PPVVSvPKGNWu09jXVNlOSLLAeD6CA13bjIelpWttSw=="], + "resolve-import": ["resolve-import@1.4.6", "", { "dependencies": { "glob": "^10.3.3", "walk-up-path": "^3.0.1" } }, "sha512-CIw9e64QcKcCFUj9+KxUCJPy8hYofv6eVfo3U9wdhCm2E4IjvFnZ6G4/yIC4yP3f11+h6uU5b3LdS7O64LgqrA=="], "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], @@ -4702,7 +4700,7 @@ "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], - "rimraf": ["rimraf@6.1.3", "", { "dependencies": { "glob": "^13.0.3", "package-json-from-dist": "^1.0.1" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA=="], + "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], "robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="], @@ -4952,7 +4950,7 @@ "swr": ["swr@2.4.1", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA=="], - "sync-content": ["sync-content@2.0.4", "", { "dependencies": { "glob": "^13.0.1", "mkdirp": "^3.0.1", "path-scurry": "^2.0.0", "rimraf": "^6.0.0" }, "bin": { "sync-content": "dist/esm/bin.mjs" } }, "sha512-w3ioiBmbaogob33WdLnuwFk+8tpePI58CTWKqtdAgEqc2hfGuSwP02gPETqNX/3PLS5skv5a1wQR0gbaa2W0XQ=="], + "sync-content": ["sync-content@1.0.2", "", { "dependencies": { "glob": "^10.2.6", "mkdirp": "^3.0.1", "path-scurry": "^1.9.2", "rimraf": "^5.0.1" }, "bin": { "sync-content": "dist/mjs/bin.mjs" } }, "sha512-znd3rYiiSxU3WteWyS9a6FXkTA/Wjk8WQsOyzHbineeL837dLn3DA4MRhsIX3qGcxDMH6+uuFV4axztssk7wEQ=="], "system-architecture": ["system-architecture@0.1.0", "", {}, "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA=="], @@ -5108,7 +5106,7 @@ "tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="], - "tshy": ["tshy@3.3.2", "", { "dependencies": { "@typescript/native-preview": "^7.0.0-dev.20260218.1", "chalk": "^5.6.2", "chokidar": "^4.0.3", "foreground-child": "^4.0.0", "jsonc-simple-parser": "^3.0.0", "minimatch": "^10.0.3", "mkdirp": "^3.0.1", "polite-json": "^5.0.0", "resolve-import": "^2.4.0", "rimraf": "^6.1.2", "sync-content": "^2.0.3", "typescript": "^5.9.3", "walk-up-path": "^4.0.0" }, "bin": { "tshy": "dist/esm/bin-min.mjs" } }, "sha512-vOIXkqMtBWNjKUR/c99+6N50LhWdnKG1xE3+5wf8IPdzxx2lcIFPvbGgFdBBgoTMbdNb8mz06MUm7hY+TFnJcw=="], + "tshy": ["tshy@2.0.1", "", { "dependencies": { "chalk": "^5.3.0", "chokidar": "^3.6.0", "foreground-child": "^3.1.1", "minimatch": "^9.0.4", "mkdirp": "^3.0.1", "polite-json": "^5.0.0", "resolve-import": "^1.4.5", "rimraf": "^5.0.1", "sync-content": "^1.0.2", "typescript": "5", "walk-up-path": "^3.0.1" }, "bin": { "tshy": "dist/esm/index.js" } }, "sha512-U5fC+3pMaGfmULhPTVpxKMd62AcX13yfsFrjhAP/daTLG6LFRLIuxqYOmkejJ4MT/s5bEa29+1Jy/9mXkMiMfA=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -5232,7 +5230,7 @@ "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], - "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + "uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], "v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="], @@ -5274,7 +5272,7 @@ "walk-sync": ["walk-sync@0.2.7", "", { "dependencies": { "ensure-posix-path": "^1.0.0", "matcher-collection": "^1.0.0" } }, "sha512-OH8GdRMowEFr0XSHQeX5fGweO6zSVHo7bG/0yJQx6LAj9Oukz0C8heI3/FYectT66gY0IPGe89kOvU410/UNpg=="], - "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], + "walk-up-path": ["walk-up-path@3.0.1", "", {}, "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA=="], "watchpack": ["watchpack@2.5.1", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg=="], @@ -5404,6 +5402,8 @@ "@artilleryio/int-core/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + "@artilleryio/int-core/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + "@artilleryio/int-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], "@asyncapi/parser/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], @@ -5414,8 +5414,12 @@ "@asyncapi/parser/node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="], + "@autumn/openapi/dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="], + "@autumn/server/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + "@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260323.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260323.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260323.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260323.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260323.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260323.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260323.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260323.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-e8rnqL5I4DUSjiy6jiWqYFKcgKq8tC4S+uEmJwWiyTgSIGDhaRUujJ2pb6EXmi+NPeXoES6vIG7e9BsEV0WSow=="], + "@autumn/server/autumn-js": ["autumn-js@0.1.85", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4" }, "optionalPeers": ["better-auth", "better-call", "convex"] }, "sha512-PDud/t8z5bDJcD7ptyHzTaoJ0A8zkxvQ4TYcJ48RtgKDdOkVY36D1T6udVLwLDnWw4J5KXwJgEuGxHdd+cuABw=="], "@autumn/server/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="], @@ -5674,11 +5678,9 @@ "@langchain/core/decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], - "@langchain/core/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "@langchain/langgraph/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], + "@langchain/core/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], - "@langchain/langgraph-checkpoint/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], + "@langchain/core/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@langchain/langgraph-sdk/p-queue": ["p-queue@9.1.0", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^7.0.0" } }, "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw=="], @@ -5752,6 +5754,8 @@ "@mintlify/prebuild/unist-util-visit": ["unist-util-visit@4.1.2", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.1.1" } }, "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg=="], + "@mintlify/prebuild/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + "@mintlify/previewing/chalk": ["chalk@5.2.0", "", {}, "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA=="], "@mintlify/previewing/chokidar": ["chokidar@3.5.3", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw=="], @@ -5794,6 +5798,8 @@ "@mintlify/validation/object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], + "@mintlify/validation/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + "@mintlify/validation/zod": ["zod@3.24.0", "", {}, "sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w=="], "@mintlify/validation/zod-to-json-schema": ["zod-to-json-schema@3.20.4", "", { "peerDependencies": { "zod": "^3.20.0" } }, "sha512-Un9+kInJ2Zt63n6Z7mLqBifzzPcOyX+b+Exuzf7L1+xqck9Q2EPByyTRduV3kmSPaXaRer1JCsucubpgL1fipg=="], @@ -6010,6 +6016,8 @@ "@posthog/ai/openai": ["openai@6.32.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-j3k+BjydAf8yQlcOI7WUQMQTbbF5GEIMAE2iZYCOzwwB3S2pCheaWYp+XZRNAch4jWVc52PMDGRRjutao3lLCg=="], + "@posthog/ai/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + "@posthog/ai/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@prisma/instrumentation/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "import-in-the-middle": "^2.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA=="], @@ -6168,6 +6176,8 @@ "@useautumn/sdk/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + "@useautumn/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "@vercel/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], @@ -6226,6 +6236,8 @@ "artillery-plugin-publish-metrics/prom-client": ["prom-client@14.2.0", "", { "dependencies": { "tdigest": "^0.1.1" } }, "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA=="], + "artillery-plugin-publish-metrics/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + "atmn/@tanstack/react-query": ["@tanstack/react-query@5.95.0", "", { "dependencies": { "@tanstack/query-core": "5.95.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-EMP8B+BK9zvnAemT8M/y3z/WO0NjZ7fIUY3T3wnHYK6AA3qK/k33i7tPgCXCejhX0cd4I6bJIXN2GmjrHjDBzg=="], "atmn/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], @@ -6280,6 +6292,8 @@ "bullmq/ioredis": ["ioredis@5.9.3", "", { "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-VI5tMCdeoxZWU5vjHWsiE/Su76JGhBvWF1MJnV9ZtGltHk9BmD48oDq8Tj8haZ85aceXZMxLNDQZRVo5QKNgXA=="], + "bullmq/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + "bun-types/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], "c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], @@ -6472,8 +6486,6 @@ "get-uri/data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], - "glob/foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - "globby/slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], "hosted-git-info/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], @@ -6508,12 +6520,12 @@ "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "langchain/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + "langchain/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "langium/chevrotain": ["chevrotain@11.1.2", "", { "dependencies": { "@chevrotain/cst-dts-gen": "11.1.2", "@chevrotain/gast": "11.1.2", "@chevrotain/regexp-to-ast": "11.1.2", "@chevrotain/types": "11.1.2", "@chevrotain/utils": "11.1.2", "lodash-es": "4.17.23" } }, "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg=="], - "langsmith/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], - "line-column-path/type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="], "listr2/cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="], @@ -6532,6 +6544,8 @@ "meow/type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="], + "mermaid/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "minimist-options/arrify": ["arrify@1.0.1", "", {}, "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA=="], @@ -6704,10 +6718,6 @@ "requirejs-config-file/stringify-object": ["stringify-object@3.3.0", "", { "dependencies": { "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", "is-regexp": "^1.0.0" } }, "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw=="], - "resolve-import/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], - - "rimraf/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], - "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], "run-jxa/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], @@ -6774,12 +6784,6 @@ "supports-hyperlinks/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "svix/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], - - "sync-content/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], - - "sync-content/path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], - "tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], "tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], @@ -6804,9 +6808,7 @@ "tsc-alias/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], - "tshy/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260323.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260323.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260323.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260323.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260323.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260323.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260323.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260323.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-e8rnqL5I4DUSjiy6jiWqYFKcgKq8tC4S+uEmJwWiyTgSIGDhaRUujJ2pb6EXmi+NPeXoES6vIG7e9BsEV0WSow=="], - - "tshy/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + "tshy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "tsutils/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], @@ -6958,6 +6960,20 @@ "@autumn/server/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + "@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260323.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3tQdgMaYn57xzRUWek+zNKMiP0z9j7fqbCjr0wlyiLNIh5jMwDArjBDKHlqSu58FKvZg7baqbqB5Mcepb3s6w=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260323.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-YE6pD4wdMqNgaBkXicQBLFwABOEmLxDclSM7grl0fw4cQSbfVwFCbSlwFDkmISKdmsgtWdYeMohrsTU710ufpg=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260323.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6zFO/SF9Gu8rtRyEt1b10TNapQGq5b/wUjCLG14675n155r4EO3JFMgnltBViV2Eatnq7G+bXD65BUBk7Ixyhw=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260323.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Jzr2gBY0ifA3XejAl7kPeHLT72JFBfzLSafOAQbANh5Iag02uPl99k8ORMfKREbYgEMMOzqPpe+r6eaKy+VEfw=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260323.1", "", { "os": "linux", "cpu": "x64" }, "sha512-UvsQdVI/LayXTowltMgtg2GHU/a/lcuOYbaAYm9+/nvgIs01VqVo0s1/lTSNBzepEk0y1EOYQ6SIsRM9lLGHxA=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260323.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-3208Xoe+3Xblf6WLVTkIdyh6401zB2eXAhu1UaDpZY0rf8SMHCxKW3TSJDytpri5UivCotZ0CNC2wgJ1TlymUA=="], + + "@autumn/server/@typescript/native-preview/@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260323.1", "", { "os": "win32", "cpu": "x64" }, "sha512-DAiRqrcs58eXwjFOtbklbIHq70IpW7uYz1Bx3kNAzqoWlA7R4mC29N6G0kGEZDalGmj7f0HVuckq9AzaC1r6oQ=="], + "@autumn/server/autumn-js/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@autumn/server/ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], @@ -7972,8 +7988,6 @@ "fs-minipass/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - "glob/foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "ink-confirm-input/ink-text-input/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], "ink-confirm-input/ink-text-input/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="], @@ -8144,8 +8158,6 @@ "puppeteer/puppeteer-core/chromium-bidi": ["chromium-bidi@0.6.2", "", { "dependencies": { "mitt": "3.0.1", "urlpattern-polyfill": "10.0.0", "zod": "3.23.8" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-4WVBa6ijmUTVr9cZD4eicQD8Mdy/HCX3bzEIYYpmk0glqYLoWH+LqQEvV9RpDRzoQSbY1KJHloYXbDMXMbDPhg=="], - "react-email/glob/foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - "react-email/glob/jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="], "react-email/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], @@ -8174,14 +8186,6 @@ "requirejs-config-file/stringify-object/is-regexp": ["is-regexp@1.0.0", "", {}, "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA=="], - "resolve-import/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], - - "resolve-import/glob/path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], - - "rimraf/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], - - "rimraf/glob/path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], - "run-jxa/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], "run-jxa/execa/human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], @@ -8254,29 +8258,15 @@ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "sync-content/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], - "ts-to-zod/@clack/prompts/@clack/core": ["@clack/core@1.0.0-alpha.4", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-VCtU+vjyKPMSakVrB9q1bOnXN7QW/w4+YQDQCOF59GrzydW+169i0fVx/qzRRXJgt8KGj/pZZ/JxXroFZIDByg=="], "tsc-alias/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "tsc-alias/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - "tshy/@typescript/native-preview/@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260323.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3tQdgMaYn57xzRUWek+zNKMiP0z9j7fqbCjr0wlyiLNIh5jMwDArjBDKHlqSu58FKvZg7baqbqB5Mcepb3s6w=="], - - "tshy/@typescript/native-preview/@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260323.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-YE6pD4wdMqNgaBkXicQBLFwABOEmLxDclSM7grl0fw4cQSbfVwFCbSlwFDkmISKdmsgtWdYeMohrsTU710ufpg=="], + "tshy/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "tshy/@typescript/native-preview/@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260323.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6zFO/SF9Gu8rtRyEt1b10TNapQGq5b/wUjCLG14675n155r4EO3JFMgnltBViV2Eatnq7G+bXD65BUBk7Ixyhw=="], - - "tshy/@typescript/native-preview/@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260323.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Jzr2gBY0ifA3XejAl7kPeHLT72JFBfzLSafOAQbANh5Iag02uPl99k8ORMfKREbYgEMMOzqPpe+r6eaKy+VEfw=="], - - "tshy/@typescript/native-preview/@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260323.1", "", { "os": "linux", "cpu": "x64" }, "sha512-UvsQdVI/LayXTowltMgtg2GHU/a/lcuOYbaAYm9+/nvgIs01VqVo0s1/lTSNBzepEk0y1EOYQ6SIsRM9lLGHxA=="], - - "tshy/@typescript/native-preview/@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260323.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-3208Xoe+3Xblf6WLVTkIdyh6401zB2eXAhu1UaDpZY0rf8SMHCxKW3TSJDytpri5UivCotZ0CNC2wgJ1TlymUA=="], - - "tshy/@typescript/native-preview/@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260323.1", "", { "os": "win32", "cpu": "x64" }, "sha512-DAiRqrcs58eXwjFOtbklbIHq70IpW7uYz1Bx3kNAzqoWlA7R4mC29N6G0kGEZDalGmj7f0HVuckq9AzaC1r6oQ=="], - - "tshy/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], + "tshy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], "type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -8950,8 +8940,6 @@ "puppeteer/puppeteer-core/chromium-bidi/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="], - "react-email/glob/foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "react-email/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="], "react-email/glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], @@ -8962,10 +8950,6 @@ "read-pkg-up/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], - "resolve-import/glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], - - "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], - "sdk-test/next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "shadcn/cosmiconfig/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], @@ -8974,11 +8958,9 @@ "shadcn/ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], - "sync-content/glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], - "tsc-alias/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "tshy/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "tshy/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "unplugin/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -9132,6 +9114,8 @@ "@mintlify/link-rot/@mintlify/scraping/@mintlify/common/@mintlify/validation/object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], + "@mintlify/link-rot/@mintlify/scraping/@mintlify/common/@mintlify/validation/uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + "@mintlify/link-rot/@mintlify/scraping/@mintlify/common/@mintlify/validation/zod-to-json-schema": ["zod-to-json-schema@3.20.4", "", { "peerDependencies": { "zod": "^3.20.0" } }, "sha512-Un9+kInJ2Zt63n6Z7mLqBifzzPcOyX+b+Exuzf7L1+xqck9Q2EPByyTRduV3kmSPaXaRer1JCsucubpgL1fipg=="], "@mintlify/link-rot/@mintlify/scraping/@mintlify/common/hast-util-to-html/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], @@ -9214,16 +9198,10 @@ "read-pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - "resolve-import/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "shadcn/ora/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], "shadcn/ora/cli-cursor/restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "sync-content/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "xo/eslint/file-entry-cache/flat-cache/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], "xo/find-up/locate-path/p-locate/p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], diff --git a/others/python-sdk/.speakeasy/gen.lock b/others/python-sdk/.speakeasy/gen.lock index 360e791fe..abdfa8845 100644 --- a/others/python-sdk/.speakeasy/gen.lock +++ b/others/python-sdk/.speakeasy/gen.lock @@ -1,39 +1,39 @@ lockVersion: 2.0.0 id: 05940b80-1ef8-40f4-9878-822fb2792070 management: - docChecksum: e9a1af9607edac6c1042359d221d1141 + docChecksum: b2a039ee0808d119f6be0c0d1504525b docVersion: 2.2.0 - speakeasyVersion: 1.757.1 - generationVersion: 2.866.2 + speakeasyVersion: 1.719.0 + generationVersion: 2.824.1 releaseVersion: 0.4.18 configChecksum: 2263d20254e354a1792248274002f650 persistentEdits: - generation_id: 5858658a-2678-4017-b8dc-74ec22fe0fb8 - pristine_commit_hash: d45b5773db5440b0a047a2faf64ad8441e828090 - pristine_tree_hash: 7d74d0d79af27f14da5af574c8cfe6671deed4b8 + generation_id: cf955dd6-87ca-4610-b2e5-d305fef5523b + pristine_commit_hash: 192f7483eda75fc42bfeeb9b13aa9f5790ef8607 + pristine_tree_hash: a466b4e81e20d68c76a1721c9ade3187c3fedbb8 features: python: additionalDependencies: 1.0.0 - constsAndDefaults: 1.0.7 - core: 6.0.19 + constsAndDefaults: 1.0.6 + core: 6.0.5 defaultEnabledRetries: 0.2.0 - enumUnions: 0.1.1 + enumUnions: 0.1.0 envVarSecurityUsage: 0.3.2 flatRequests: 1.0.1 flattening: 3.1.1 globalSecurity: 3.0.5 globalSecurityCallbacks: 1.0.0 globalSecurityFlattening: 1.0.0 - globalServerURLs: 3.2.1 + globalServerURLs: 3.2.0 globals: 3.0.0 hiddenGlobals: 1.0.0 methodArguments: 1.0.2 - nameOverrides: 3.0.3 + nameOverrides: 3.0.1 nullables: 1.0.2 responseFormat: 1.1.0 - retries: 3.0.4 + retries: 3.0.3 sdkHooks: 1.2.1 - unions: 3.1.4 + unions: 3.1.3 trackedFiles: .gitattributes: id: 24139dae6567 @@ -49,8 +49,8 @@ trackedFiles: pristine_git_object: 338e57de579fc8a219b6580798c5632db01380b2 docs/models/action.md: id: 3b583d6a609e - last_write_checksum: sha1:098b94f92de64e0d8913334d0889fd85ebe460b6 - pristine_git_object: 7d100638aec1c657cb7cb6696ec0818b19661e80 + last_write_checksum: sha1:72773682ff50533bc180ba2562b9a99251b0ff46 + pristine_git_object: 2254d6362f569f198c928970f3bffa4a02a78730 docs/models/aggregateeventscustomrange.md: id: 6bcfabf82b0c last_write_checksum: sha1:db82c5b951354ad2d10f2346cf57b098402df524 @@ -73,8 +73,8 @@ trackedFiles: pristine_git_object: 2fd7b5fb0f426ec942cb43aa30d3a85287ef724a docs/models/attachaction.md: id: 984e5ad5e280 - last_write_checksum: sha1:162c4a0771f000b3835c06765eafdd843fb8d2a7 - pristine_git_object: e9209701657b1cdd903b97261db2df27102eb9d6 + last_write_checksum: sha1:04e00a9143d9f5d350f7bec1cfe6651e07217f90 + pristine_git_object: b358eec0b5b32c184314a5c9b170f3074d577a55 docs/models/attachattachdiscount.md: id: 1283b5fb2544 last_write_checksum: sha1:fe32155e714211074643a30f70794924db11d3d7 @@ -85,8 +85,8 @@ trackedFiles: pristine_git_object: f680f716064012181c133cd1e21fb92b1bfc0b60 docs/models/attachbillingmethod.md: id: 99e45152f80b - last_write_checksum: sha1:e983973f9ef8a20614a75371e4c3fd0d4c477274 - pristine_git_object: 9bcf8a8f9f309f3eb1dbd23a03ba9e96fc7b46ba + last_write_checksum: sha1:b8221621e406f78f522156e0fa70d60014ad368e + pristine_git_object: 674a4b1c6077466a9b4d228fe743054b34f21087 docs/models/attachcarryoverbalances.md: id: e9af90ccd79e last_write_checksum: sha1:b6d60b17d15773c512653245b70f4a218d6b8b3f @@ -97,8 +97,8 @@ trackedFiles: pristine_git_object: ad5200ea1d15b517606e8f16bb2af11982b62f5b docs/models/attachcode.md: id: 1d55f798a682 - last_write_checksum: sha1:fccfcfb5a4d6ca2d6e54086340042de8e6ec0897 - pristine_git_object: 278b7f044f6b8e69ba9eb7ebc9d584425b0b758c + last_write_checksum: sha1:dd5a0adc97a023eec27540fe9bb590334ebcdcc2 + pristine_git_object: 1a1195e26b91cbb4948aa79005efbc85197c4d1b docs/models/attachcustomize.md: id: 46261b03538b last_write_checksum: sha1:8ae92012b8fc0197f260af1dc249f6bcede5447f @@ -109,12 +109,12 @@ trackedFiles: pristine_git_object: eba02df5a4003fe30622c5d39f4f972b73db7943 docs/models/attachdurationtype.md: id: 9e410a17d56b - last_write_checksum: sha1:b4b984d5cfbc57a83e5c2b6250da03ff9d3b0367 - pristine_git_object: bccfc3c11061b31ae5def04e1c32fb1b0f717401 + last_write_checksum: sha1:201a2d77508a63c17506d5ba9370d2736a8e2024 + pristine_git_object: d9256b21f869bf4d7fab1915970481f152437e98 docs/models/attachexpirydurationtype.md: id: 524cd5ddcdd5 - last_write_checksum: sha1:e76e5ff587821238b170d53fa37eb6b21c4640c4 - pristine_git_object: 4886da097384dc3a6a110e0d94fd2208556db9a6 + last_write_checksum: sha1:91fadc6920cc988fe2e0b5b02e56a137a3973396 + pristine_git_object: 96116d8755118d92cc4250d33231226b17e56eb7 docs/models/attachfeaturequantity.md: id: 5c9fae37c74a last_write_checksum: sha1:7447c83e4931c61fa08d0576e7e4390a363b448c @@ -137,16 +137,16 @@ trackedFiles: pristine_git_object: 5d7416d60f95d5c4c61f875c61abc909fd861647 docs/models/attachitempriceinterval.md: id: da3ed38a03d3 - last_write_checksum: sha1:8617ac575d72925866f581409bbd40721bfc20c2 - pristine_git_object: 82da5075c879543564d63eb2ddd16b019ca3a731 + last_write_checksum: sha1:7dc2a9864c52c09ca9e59ff09f7527345794d372 + pristine_git_object: c49d88c4c7696afe17566afb55b3eacb85bdc1b5 docs/models/attachondecrease.md: id: dd7dd5f2b175 - last_write_checksum: sha1:2b0fce8cff2835de026a9efd8574d0af5e731b02 - pristine_git_object: 5f6880b990bf2a7d57c16a07531d1e6d22961417 + last_write_checksum: sha1:c3d1c6a51c901ed80d02d4449f70ce839bf062fa + pristine_git_object: 2eb0944df8025fc478b80f965b0bda2ec9556c18 docs/models/attachonincrease.md: id: 8db7e1552d9b - last_write_checksum: sha1:e761df4e98dba4e4241a20c48cdaea2bfd9d9c89 - pristine_git_object: 4b75b3d142ea7237687dd5d3dd6a0e463186bbcf + last_write_checksum: sha1:7b90839a3f71de9a1b928b5cbb973fefdc2e4b21 + pristine_git_object: 0640ae26af1990b129e9d92f1f84c5b93dbe91e4 docs/models/attachparams.md: id: cc19aeb7efcc last_write_checksum: sha1:9ed344bd9f8aad64f2d3da9ac3946f44808f6130 @@ -157,28 +157,28 @@ trackedFiles: pristine_git_object: b0c7d55d66c6f3db5395dbebbfe6184f5ad9a820 docs/models/attachplanschedule.md: id: 5dedfadc5aa9 - last_write_checksum: sha1:158769029df8d0c3b0d57f044c93bbe6ac07a6a9 - pristine_git_object: 9e9142dc8bb2ecce0d1202d6774b4177a545c282 + last_write_checksum: sha1:2a3ba63683284a780e3271e87fefa1b5454d6d14 + pristine_git_object: 50e9fb97e031ae1980846b152a3aa8f5d04e0793 docs/models/attachprice.md: id: 5ac4c60e2ff1 last_write_checksum: sha1:ce20a5bbb0abafc24c72e6ed2f9ceacad6679eab pristine_git_object: 82e6a2748fa31e66759be781b6a8c0cbe60345f6 docs/models/attachpriceinterval.md: id: 8f4444fe4a59 - last_write_checksum: sha1:2d6bad7161b52bc32eaea682a20c915c51d8d155 - pristine_git_object: 0a6a983e4d750e436c4cc8e3bc8f04d625f97f40 + last_write_checksum: sha1:667c436605831e4482ea2ec4d8c1f3412baf3f56 + pristine_git_object: 8dbf9d9e66ad45a3c9b7928ecdc563a6a6f33ac8 docs/models/attachproration.md: id: d92b4c3cafc9 last_write_checksum: sha1:afb0910fbce9bf9150b725ea18333deac67a86a4 pristine_git_object: e4108282ef057841da4ce7c0b86014b093540c6f docs/models/attachprorationbehavior.md: id: 47599392eeb0 - last_write_checksum: sha1:3a04d2f088bccd574f05d49d9c5c21e7b8ed0861 - pristine_git_object: 925ea57b3dc4fb417c734ad9eeb3c161d2cfe68a + last_write_checksum: sha1:dffce59b18293fa90357642373404b09c5075631 + pristine_git_object: 73d6811a4272a920eaaf4fe92b9b956fcf1e32a8 docs/models/attachredirectmode.md: id: 335591f45026 - last_write_checksum: sha1:37faaec09ff06df901b61d54bfda8d9ccc014322 - pristine_git_object: a339d05aa7f3974607b4a5a9cab52883ad9ac3a5 + last_write_checksum: sha1:41a62121f86f18752a935e6b0b53570187514d78 + pristine_git_object: 0ca0543fee7eea64ff4c3eba17957936af819f76 docs/models/attachrequiredaction.md: id: e8ac88409626 last_write_checksum: sha1:d6e507f9cd1e47e617c13b69ab474f8dacd63c36 @@ -189,8 +189,8 @@ trackedFiles: pristine_git_object: e406e5ad17a15e88a409c3b1d4715dc367d1c6dd docs/models/attachresetinterval.md: id: b66e6fdb0dff - last_write_checksum: sha1:5829670189f8f07b56ae9b4fdb45530b248fa234 - pristine_git_object: 55acc0c8bd8c0f0a1ef9a99ae165a6ea5346648a + last_write_checksum: sha1:ce7b9746fcc351e855e23a01da8b484d752fe68c + pristine_git_object: d3a411212a7c9a04e791258b5c7b4d5025abbe47 docs/models/attachresponse.md: id: 8badce83e7c2 last_write_checksum: sha1:9b71d935c1dc9f81b2e5f1e8718ba2fe7737e27c @@ -205,8 +205,8 @@ trackedFiles: pristine_git_object: ac25813f9c9cd40bafa3fb187e30e61df7d6bfad docs/models/attachtierbehavior.md: id: dd4442a54a75 - last_write_checksum: sha1:6962ba9fa9fc4bbc5f8441a596e76b580850783d - pristine_git_object: 00cce5677a649f8df640605da745bb08a9ae5a52 + last_write_checksum: sha1:933eae06cb0235ae6d3c89de2d3ee2ac1d9ea35a + pristine_git_object: 3a306087ef917c5715f0d6c98f54682299662e7d docs/models/attachto.md: id: 1b02aab4ff64 last_write_checksum: sha1:808ce310ce6ff60548951c33b708bd7b5ea9a67d @@ -217,8 +217,8 @@ trackedFiles: pristine_git_object: b1aefcb1047dfb9c731d1518cbdf74e9372b4e0f docs/models/balancebillingmethod.md: id: 76fb2d92a3f9 - last_write_checksum: sha1:63c9d33198f6096715f3eb15a0b95ca0577d3fbf - pristine_git_object: 18ddc4890f476436e7bfbfdd04678613368293d0 + last_write_checksum: sha1:43baa9646b3939427ae05abfca22cbdff74a35ea + pristine_git_object: c226bd34b47d5c7e7c2f7515545e739dd09c2e44 docs/models/balancecreditschema.md: id: dae51ba88e27 last_write_checksum: sha1:0a21dcca4349bae735898fc945098b921ae57892 @@ -233,8 +233,8 @@ trackedFiles: pristine_git_object: f2c3a195899b943768528924a018dd292e021fc7 docs/models/balanceintervalenum.md: id: ed4c1f705227 - last_write_checksum: sha1:77b828fd35efe794358527090fa4d9ad5e3e0049 - pristine_git_object: a61d3a48fc7daf0eb1e4db4626514a90a77b88b8 + last_write_checksum: sha1:e7904ec75c5f69afb14c17885ad9c89dbf9f0122 + pristine_git_object: a65f5523a040e8a83f482d5d4a257b3954615d3d docs/models/balanceprice.md: id: b299e024076f last_write_checksum: sha1:feab76c2289c4d0e195ac5a4c79def50190d3ca7 @@ -249,40 +249,40 @@ trackedFiles: pristine_git_object: 43c658b3efea42752d73bd913e72a865bb7d9125 docs/models/balancetierbehavior.md: id: c5d6d94e4c04 - last_write_checksum: sha1:b5b090556f2b01e915f8947d23cf3b86b035b49f - pristine_git_object: 90f2ca6d455e580585ed339a4eecc9552c55208a + last_write_checksum: sha1:8c6caa64e60d7c93bfaffaa25cb758b61547d75e + pristine_git_object: bdcf40add36d9f3a50497013067d69e50fd30724 docs/models/balancetype.md: id: 75c2cae544fe - last_write_checksum: sha1:09514e05b48bd58bd5f6a2bd6f33ba3f0b0ad957 - pristine_git_object: 15b56e6a26511479ac90168ff02e708073684ad4 + last_write_checksum: sha1:421b637d3c78e46253b1c7e2647f6ff87044445c + pristine_git_object: 593a752a830a4abd82ce47a483331b88395e9835 docs/models/billingupdatebaseprice.md: id: c632c15479cf last_write_checksum: sha1:f6208715f5d3eea0ffb73dc8bfd4135c9b278255 pristine_git_object: f26d6458df8d6d17c0e3d14b40cea37100ecbf6c docs/models/billingupdatebillingmethod.md: id: 12159f4a0d20 - last_write_checksum: sha1:0b777e760bec198d3424ac3264f729185d0404cf - pristine_git_object: 17c5fbf6b42345e437c1ea9acc642da096aff400 + last_write_checksum: sha1:21bba85f59699f5bf1e8497ecce35e67b70fcac0 + pristine_git_object: f3d2efee7277bf813f083016c499077f4c3bc474 docs/models/billingupdatecancelaction.md: id: db6ed95035d6 - last_write_checksum: sha1:602b218add93ba7c1321fae1519f4c71e742067a - pristine_git_object: de773d6ff842d49bad76dfb878a4f4a5b9f12425 + last_write_checksum: sha1:6781ff5f812623eb2d4c362ab478433515f16003 + pristine_git_object: 7e74a4b0dbbd3d2665ee3444031ca58e2f3262b9 docs/models/billingupdatecode.md: id: 9b4598eb634b - last_write_checksum: sha1:ab83e37b31683591f116c230145ba361bd2aba67 - pristine_git_object: b84d2bb18ea93dcf6314b5d3193dc413e94e897a + last_write_checksum: sha1:58ca1664740b44d0eca145f2222d801423c9ed22 + pristine_git_object: ed6a76be783d37f8b7dab9629f4e4b24dd944d0c docs/models/billingupdatecustomize.md: id: 72b14fed6d26 last_write_checksum: sha1:c87d10da6d0f4468644d1d6ded6f105bee61dc5f pristine_git_object: 52bbdf8edcc89477c447f910b8d8bf5e9f6a9684 docs/models/billingupdatedurationtype.md: id: fd1bfb929148 - last_write_checksum: sha1:4ecc7d22519fc4f6b45ca394942f2e2bb7d6e008 - pristine_git_object: be30454f05e84698e39999db581e7d41ceb6ef4f + last_write_checksum: sha1:bed8f44bace7e7187c9a6510549b1a25e0893db8 + pristine_git_object: 29c75dda04da8317052ea821f1a2954a9572958e docs/models/billingupdateexpirydurationtype.md: id: 53d2632a7c8a - last_write_checksum: sha1:47b570d582188a62b914297406dab2267111105a - pristine_git_object: d2c185e5c8ce8a6fe06f557bd9eb1c3c3b2d0c42 + last_write_checksum: sha1:7258e9d4c17f5966c6ceaa20e71963da5e77bfe4 + pristine_git_object: cf21b1895d1b383d88b984f4c62f881d11b8994f docs/models/billingupdatefeaturequantity.md: id: e799ad33bb5a last_write_checksum: sha1:c65f7f8271bdffbf5182af05e9ebdeecc37e42b1 @@ -305,16 +305,16 @@ trackedFiles: pristine_git_object: ccc987c410b54cdfd945b0a4c54020614b9d3e65 docs/models/billingupdateitempriceinterval.md: id: 0cec75e3f2ae - last_write_checksum: sha1:a0b1cee7b78bf47a51bdaa18f82fc83082474c5c - pristine_git_object: adf8b59604bd7afd87a739d39d30e1fb92aef798 + last_write_checksum: sha1:23f7e1aeb4061a35ca0ef1008a182d58d48ce07f + pristine_git_object: 876d9175009bf7588f4c069b81e9eaa9ce8c13c0 docs/models/billingupdateondecrease.md: id: 1524bf987a6a - last_write_checksum: sha1:ad8244fe62d4af25862d59c4ef2d70dcac190b7c - pristine_git_object: 0d76e192a32de2b827ebf629924e8fcc682d2094 + last_write_checksum: sha1:61be180db7fdabd7c4deb67c479101c5a2d71d3a + pristine_git_object: 20bd4b901deaa9b626423150dd187ce2d81846d8 docs/models/billingupdateonincrease.md: id: f43ba81c2c74 - last_write_checksum: sha1:8f355eda384db6aec10dbd583f2446689bb7dbf3 - pristine_git_object: 6b5a79d32eb477e8449f09e61cb2e758831cb858 + last_write_checksum: sha1:bde4ff3aac2375c4116af12c88c7a66cc7adabb3 + pristine_git_object: 2ae60a2cbc19c1d08f22485f371ef2eb49b3eab7 docs/models/billingupdateplanitem.md: id: 2a54ff8cadb0 last_write_checksum: sha1:1acd9eeebc8041d0298d79e4bfd8534b0b497aa6 @@ -325,20 +325,24 @@ trackedFiles: pristine_git_object: b7c4d7f98430c5f6cad62591307b6616aa9e19d2 docs/models/billingupdatepriceinterval.md: id: 35fa4e54bdfa - last_write_checksum: sha1:c0da44caf43fa226e1f4c61f66105a12dfaf7799 - pristine_git_object: 5bf583ddc236556a6cc1bf48870860d05db61adf + last_write_checksum: sha1:f800390f9ff92064f4cfd8a7a9e1a974e9735ae7 + pristine_git_object: fef99c2a4d614825ca172aac2fbad7232f797067 docs/models/billingupdateproration.md: id: ceef03cb88fe last_write_checksum: sha1:349ef125fcb651dc625984c657e2b2904443fdb4 pristine_git_object: b08ae1cb77d8adce2b494a8c9f8d82458b444e8a docs/models/billingupdateprorationbehavior.md: id: 59e4a7bd5f57 - last_write_checksum: sha1:4f839363f995381f5c7b5e9781970c7f6b89cac6 - pristine_git_object: 2fe315798c7425eea4b333ea5a3be11455a5e45d + last_write_checksum: sha1:25cc1dae82375b1355bb8c0258f8345a2ab2749a + pristine_git_object: 605a70dbef1054518e8bc4247cffb2cd78ca9f9e + docs/models/billingupdaterecalculatebalances.md: + id: 1efafc117462 + last_write_checksum: sha1:814d945540f0a000cd468ab8c050dc04af751cf8 + pristine_git_object: f23cde4509ce1e504a1bde766cb403ab35fd4d1e docs/models/billingupdateredirectmode.md: id: 9043a4aebf85 - last_write_checksum: sha1:225186c2fb33963411f365af1d6f4027e9090d8d - pristine_git_object: 98f3b96b091cd701813df6f7f5b1063fcd7c2c49 + last_write_checksum: sha1:ce637ed16ebd5095db5b5d3adcb8328018ad68e1 + pristine_git_object: 6a04e02359a17eab6227afff69b20d2d88b5708c docs/models/billingupdaterequiredaction.md: id: 3f84a7bd8718 last_write_checksum: sha1:999727446d5c53ece4c816c145063ce6f0ab0ee0 @@ -349,8 +353,8 @@ trackedFiles: pristine_git_object: 39ab16e4993be894423a09a9d2b726b40152cd21 docs/models/billingupdateresetinterval.md: id: 6031d36eb382 - last_write_checksum: sha1:b4e834b3638a22a0440ef50a13658936813746c9 - pristine_git_object: c60e227356d9d41dbde8517bdc61459c23f54165 + last_write_checksum: sha1:eeebd0210c9acebb03067c6ecea686ae95569312 + pristine_git_object: 8fbdcfac41ad16f89bf3ff8bdaaebb2dfeabaa72 docs/models/billingupdateresponse.md: id: 61961e78dc41 last_write_checksum: sha1:3c7a336951e5b2349ec5f69ff87fba2f894f98e2 @@ -365,16 +369,16 @@ trackedFiles: pristine_git_object: 36a8fe04fed81da560d7c510824a87772ea104c4 docs/models/billingupdatetierbehavior.md: id: e232f14f3859 - last_write_checksum: sha1:9255c48e25a922281551b4775e3876ed09e01173 - pristine_git_object: 93e790dd724353b2321f214c97c39131502c54a0 + last_write_checksum: sha1:e04c169893bec388a599fff7e60d82dbde0ff633 + pristine_git_object: 70caee9bb271631dfca70f5a09d91c4981249b85 docs/models/billingupdateto.md: id: 2dbc89773256 last_write_checksum: sha1:ac60ad0fd88dab5a347f21563cad039fdf8719c4 pristine_git_object: 1d40f1e0e3af491b7e67142f4d46d7e6bd68377d docs/models/binsize.md: id: 63f34acef7f6 - last_write_checksum: sha1:58322acf712532406cfae8aacc0011d342fc0e42 - pristine_git_object: 0d7ffd05f43810c7972b53983a26562cf1edf375 + last_write_checksum: sha1:310b19b9bf005367f9bb9b8522f236b116d4c8de + pristine_git_object: a5d47c7f43395017abd9061c252a8ff8ed986985 docs/models/breakdown.md: id: 786823ab8ff0 last_write_checksum: sha1:8d7542dc9cdab711fa44b08449f023cf51d0bf2e @@ -385,8 +389,8 @@ trackedFiles: pristine_git_object: 569f641aa37bd931f49d93ecc912d48d01ced164 docs/models/checkenv.md: id: badd8ba846d9 - last_write_checksum: sha1:cc58aa52d618d5718048c72c1d832c22f4fa4d44 - pristine_git_object: 2ecd490b20e9ddec41e2de895dc878bfe1e735f5 + last_write_checksum: sha1:0151f2e60f1949039194de96b878d440e0cbc7df + pristine_git_object: 063b53ed4d0dc95e2b45e40834f12b7da38014de docs/models/checkfeature.md: id: 578a088d181f last_write_checksum: sha1:79c138d673342ee61ffdbb8befdf15d35ef9a6da @@ -401,8 +405,8 @@ trackedFiles: pristine_git_object: db1b11678847be436d8d702b1dfe38fbadfc0b8c docs/models/checkinterval.md: id: 8d4fa19b99cf - last_write_checksum: sha1:1de9a5b6c8246013c331c029eff593c7d9107a0e - pristine_git_object: 7ab99824ba2b1ea51adcc3e6e1f8ed71b9a41f41 + last_write_checksum: sha1:190b1c37ca471b3afb3b61737b121032c99fa9bb + pristine_git_object: 188519a4cdc6cc4b616dbc0f4dc4c7284bd8a6d4 docs/models/checkitem.md: id: 17b1916ed078 last_write_checksum: sha1:777d0bcb69a475b1fdc64a491604f77c3892cbd4 @@ -413,12 +417,12 @@ trackedFiles: pristine_git_object: 6a029c8c5367cce65667868341d1026f540f416b docs/models/checkondecrease.md: id: 2c49735aaa4e - last_write_checksum: sha1:fbc0d73b600c664d71588db64a01f63ae9730c6f - pristine_git_object: cdbd3eee0b4829c3574a58eaee0919e3456de0c4 + last_write_checksum: sha1:957ef5f0c483f390fab1bd6bb9ad1f21697d1898 + pristine_git_object: 924902d404be8c5cca46410e8bd6d16858feb14d docs/models/checkonincrease.md: id: 72e23013a321 - last_write_checksum: sha1:285a9c0f5eedf50bf8345017cb5281054394fbf3 - pristine_git_object: 9e7cf1470360e6e191df1c53f875ddc2efce56b0 + last_write_checksum: sha1:72f018b178859f870d47e4c482ad9f734591d2c6 + pristine_git_object: 188c047ce6cdf1f916ff0e22079a8fed3e3d8916 docs/models/checkparams.md: id: 41de438d57cd last_write_checksum: sha1:ab7c54bfe0c657851a59fedd447c99bb3acdb4c2 @@ -437,8 +441,8 @@ trackedFiles: pristine_git_object: d41a6997c8fa42b8d2aba6ad2a63ad74af9ce769 docs/models/checktierbehavior.md: id: 9617428cc802 - last_write_checksum: sha1:50402db6b459dee47f0d1286dac1a86ca3608824 - pristine_git_object: 269d3333807ff7d70963551a083aa7ea5f8f397a + last_write_checksum: sha1:fa41c52ec15e0f7cb7d859c0fa26112ed11f5e91 + pristine_git_object: 07d0ee6f485f4056f4e301174ad0e7a37143e032 docs/models/config.md: id: bef254bf823c last_write_checksum: sha1:39b253e50c394f0b9bdf4f3edc73d874b5b1777c @@ -449,8 +453,8 @@ trackedFiles: pristine_git_object: e6e79f4e93370dc83282c623a6f44d19c89819eb docs/models/createbalanceinterval.md: id: bc0616b97b83 - last_write_checksum: sha1:30235488c4a82c0fe10fc60d7e8b6a918b72c823 - pristine_git_object: e39a9187a872a4ad57c7a08e78ef7c53dd344497 + last_write_checksum: sha1:1f4c49bd045c70e1562b1316837856a62e07a93b + pristine_git_object: e335f5306f33da5881881ffe48510fbc90672b3b docs/models/createbalanceparams.md: id: 9091a9b23319 last_write_checksum: sha1:bf6be59205f8f9574a5c0206f4e15551177407c8 @@ -465,12 +469,12 @@ trackedFiles: pristine_git_object: 073f7e796daafe7be8710624ee76d22d7a8b7145 docs/models/createentitybillingcontrolsrequest.md: id: 81bfb7ab2ff1 - last_write_checksum: sha1:27a4149c82ead6c1664c5a34bd7667d0ff0f1b69 - pristine_git_object: 15936d052b139c78f010480496ba4fd845b13169 + last_write_checksum: sha1:7c6cda7d6e0a41dc5c5323d527b4713fb621dcd3 + pristine_git_object: c23fef26c1dd8422d33747fcfb5ea11a5d9d54fa docs/models/createentitybillingcontrolsresponse.md: id: cb4c5b406c38 - last_write_checksum: sha1:810db06fba122ecec3b9dd10a51d5e3672262548 - pristine_git_object: 6594e1414eb2029f22426a9f623e4eb6b9ebbc97 + last_write_checksum: sha1:e70fdfd2f4229e07a06b58b22622e216bbfe2fd1 + pristine_git_object: 306b05bd5822b4dca5b488aa4c194bf3f06ac26e docs/models/createentitycreditschema.md: id: be01ed32c75d last_write_checksum: sha1:f5aff2afb245821c6acee3b54af1a9e03ddc7497 @@ -481,8 +485,8 @@ trackedFiles: pristine_git_object: 711fe44f735d330501bbbe76cd793b9c2755e0ea docs/models/createentityenv.md: id: 542876d21db6 - last_write_checksum: sha1:17bd7dd672b53ce44c9f54e5ef1b79aa4c98512d - pristine_git_object: 5d6211aeda9b6edf22bdacc3753cfad11b80126b + last_write_checksum: sha1:b73f6dc451b4e5ec7c9f9c71cd8bb517c176ad52 + pristine_git_object: 32f81e68dc328b00f43653be74163dcf7053f555 docs/models/createentityfeature.md: id: e029c7ffe3b3 last_write_checksum: sha1:bf16dd1e2a5f791e737caa9a6ad3a492be32ef0f @@ -521,16 +525,32 @@ trackedFiles: pristine_git_object: 4004ffb5832713e76b8a6b176469631039bba606 docs/models/createentitystatus.md: id: 145efa808359 - last_write_checksum: sha1:3695fcfd019e564c095560b4b1265643a5f9b285 - pristine_git_object: b3778ff16df6323f7493fd94455681c833b66091 + last_write_checksum: sha1:72bb5c58fea5e9dc436e93e1638e6817030ae3ce + pristine_git_object: 9a91415c612c744fbc62d49d23046320057421eb docs/models/createentitysubscription.md: id: 8aa96fa32fbf last_write_checksum: sha1:8ec950859a22ebd2187914d27bd442f3c0452116 pristine_git_object: 9586cfc0faabfe8e90fad4a79098f124ac3649ac + docs/models/createentitythresholdtyperequestbody.md: + id: ed52dc34947b + last_write_checksum: sha1:542ccbde26267fba9051a3d643fc40053732b573 + pristine_git_object: 78b02d2d87153b4fae5580a2bf322a3fe79e5885 + docs/models/createentitythresholdtyperesponse.md: + id: 52891c7cc403 + last_write_checksum: sha1:077929b1b4e38f3fda710be0aeee3d8cbce11112 + pristine_git_object: 6ad3db991458ca94aa4d0f6a2f5753ab9d872f41 docs/models/createentitytype.md: id: 32635d8bbeec - last_write_checksum: sha1:23b40023f5a46fd8d8309b7bd11cb2c16168601d - pristine_git_object: e9289481db3fca2916797cd0583a9b407c6c9dec + last_write_checksum: sha1:fba880b57b7e4267002a667047cbd7379b23ff05 + pristine_git_object: f761de0133e3ca5da5d7dc0809022ba2c2df72da + docs/models/createentityusagealertrequestbody.md: + id: 6c9e600d7cb2 + last_write_checksum: sha1:55bd55e606ab494a412d311cce18409109277fd8 + pristine_git_object: 80a9eadd5b83acbe72d53567ed277f36f8bca75e + docs/models/createentityusagealertresponse.md: + id: 19c3b14a71d4 + last_write_checksum: sha1:015e39c299a6a985eac38382b76fc917253bea9e + pristine_git_object: b7bed983dac8106af2f6327f1bc11e338900cf46 docs/models/createfeaturecreditschemarequest.md: id: 14932a0ada38 last_write_checksum: sha1:3ed6eb2080d0f207c81415025fca068be6dd3e6b @@ -561,24 +581,24 @@ trackedFiles: pristine_git_object: eaaac3dc55e7e703c2801c86f267cc84cd1463f2 docs/models/createfeaturetyperequest.md: id: fe6cd361268e - last_write_checksum: sha1:691716b1b97a99fd39d1c6fac575ad6e7d3a8d48 - pristine_git_object: 38d707117cd84c9b5fde3360d169f476107e9271 + last_write_checksum: sha1:f78d41e61f5bbe3251be89c0bbfa057d878d5309 + pristine_git_object: 7666bee271876460789f89488630ede75a34040e docs/models/createfeaturetyperesponse.md: id: b48617bd07bb - last_write_checksum: sha1:77ac606e5f0b689983ff6c609fdae20ef2ac2fec - pristine_git_object: e4428c360251496590101bafe621d2622acb8074 + last_write_checksum: sha1:cbae35ce696b95160ace214ff4a3729e9bb31620 + pristine_git_object: 6c35a4a0f2792209d946d20c80d30b2e024447fa docs/models/createplanattachaction.md: id: 51fddcdddcfe - last_write_checksum: sha1:9418a9072d39278ed789c16429958ee8b4e675d6 - pristine_git_object: 18912b7d8ea75eb28d8c7b26b260cbe0b7b3e5ac + last_write_checksum: sha1:76c48bcbf392473ff6f5a56106d3701bbd50f6fb + pristine_git_object: 345f2d19573e26f36c42cde6f8465c705184d1eb docs/models/createplanbillingmethodrequest.md: id: aad7cd71a9d2 - last_write_checksum: sha1:7ca5d093290ef5c5cfe9c1a912557e1712130470 - pristine_git_object: dbc67b61a533d4902f48db8a2858b5f6d7070d7e + last_write_checksum: sha1:1f2c67600db50f95337d7bf10c639e908c3fc303 + pristine_git_object: f29aef01df9f6dd537344c49f397a523c7501d9b docs/models/createplanbillingmethodresponse.md: id: b307da22d211 - last_write_checksum: sha1:cb2e4a736b4ecc159767a293453ff87f9ed0c9eb - pristine_git_object: 4a0d24cd6c7d3f100ff059751be33862a199c471 + last_write_checksum: sha1:3fcd6017bc7cb96ce77ca920fe451f9a4883a5c2 + pristine_git_object: 39cd257f0f31b76bcc61c394f1fe4bded4a0b382 docs/models/createplancreditschema.md: id: bda39f39fac9 last_write_checksum: sha1:8d3bf3dfd849ce3112674c8108d75e77bbf40d92 @@ -589,24 +609,24 @@ trackedFiles: pristine_git_object: d2a842647d543d4989a1efaafad2869698f7a2ac docs/models/createplandurationtyperequest.md: id: e21beb31d05d - last_write_checksum: sha1:2e1053332ad2fffa785d83b2abbd2a2c2819d83e - pristine_git_object: ec28bef94c8054678f4281cece7380def75172c2 + last_write_checksum: sha1:60840dd5ae3e89cfaa4c6ea23e8e5a604558a533 + pristine_git_object: 83013099132a30036afccf717a1c2c5d2b21bd23 docs/models/createplandurationtyperesponse.md: id: 1489338c4397 - last_write_checksum: sha1:56f707111b6879c0e705055c38a399d42245fe4a - pristine_git_object: a8382826dee5bb5e17d0ee5fd0382deebd31f738 + last_write_checksum: sha1:0f119191f861ab525cfc3408963d498ead41c6a4 + pristine_git_object: 19341d579df11c5ecd96cd8f2997df9b81677719 docs/models/createplanenv.md: id: 7ddb245035c3 - last_write_checksum: sha1:8f0361ff0433ad6468bfbd5f284cf446571cbcd9 - pristine_git_object: 7343391ed2b1ae2ad30b1f140c9616eeb77555f5 + last_write_checksum: sha1:8ac8cbf49092cb5887f4403fdad7b8bef8dfd247 + pristine_git_object: 0d52c5ca2a9e155d27feb55cc101f0b62c76d9ba docs/models/createplanexpirydurationtyperequest.md: id: 8bce768b39f2 - last_write_checksum: sha1:613b0344d6211e5da5ac517f69103bc7cf87f01c - pristine_git_object: 897223bad7e0b51d451ab358e47720e862274374 + last_write_checksum: sha1:628b35272d8ce2add899c6c19aa96be3d2a42a0a + pristine_git_object: 54bb9e36c6284852b4ce37e4149abb49ba3481bc docs/models/createplanexpirydurationtyperesponse.md: id: 00879e9d2363 - last_write_checksum: sha1:626ca951ef32e1019dc32dc7b8f183cc277366b2 - pristine_git_object: 0378b9b88c15b3dacf83838e35a47a7bd8e9ff54 + last_write_checksum: sha1:1310562642734128d164b805256f66a2dfa1950f + pristine_git_object: e752ec9afd9d7423ad5a5e52bba7c879930e5a0a docs/models/createplanfeature.md: id: b48fb179ae52 last_write_checksum: sha1:9b823d276ad5c6b6bd26d7802f91a83526c2f1f8 @@ -633,8 +653,8 @@ trackedFiles: pristine_git_object: bd384b452456016555d00559f6b3dab88c1ea4d2 docs/models/createplanitempriceintervalrequest.md: id: 18e0dca1ea6b - last_write_checksum: sha1:a789b4621eacdc2744f6257d3d2ec7c5a9c124ad - pristine_git_object: 5894f12d49cfaec84941e5612027928689d60d7c + last_write_checksum: sha1:f022dc04fb84189001d091aa855f585d0b912bc3 + pristine_git_object: 5ab2ea0a0a614abf498ef68a3365d1c64ac01613 docs/models/createplanitempricerequest.md: id: baaf8916c3b6 last_write_checksum: sha1:b3294dc1d5c7340bbecafacf66d64957b530a7d7 @@ -645,12 +665,12 @@ trackedFiles: pristine_git_object: 0e45fcef8a9c84780166602dc20ffbbbcd1463c1 docs/models/createplanondecrease.md: id: 13e92221faaf - last_write_checksum: sha1:afaf4c77970e09e2f8cba839ca6a5a4eab7ed028 - pristine_git_object: 43784ae3c14f6d07d58561744d304ff5c2a1f667 + last_write_checksum: sha1:c384c642ec8b7025a2e0fc74166e2153e417c173 + pristine_git_object: f4f2fd1c2dc8703409e66dc667956032309a2536 docs/models/createplanonincrease.md: id: de4cf324fe3c - last_write_checksum: sha1:66123ea9d2e41d8eec9df9dc11822c86dbed94df - pristine_git_object: a7cbec89d5e586f2e506b7fac6a14d299e227ee2 + last_write_checksum: sha1:5ff8337e204975963b88911abe70e986a40f7006 + pristine_git_object: 32d69248af29180c404ad5b0343a963e276ddcea docs/models/createplanparams.md: id: 83565eddc151 last_write_checksum: sha1:a585f44a9afc78624bda890e35c8fa50668c4248 @@ -665,16 +685,16 @@ trackedFiles: pristine_git_object: c329aa588b3946be41c98de8407001fad94759d1 docs/models/createplanpriceintervalrequest.md: id: e2978f16aade - last_write_checksum: sha1:be607cfb579147ddd94dd1a82a192fa825ae8355 - pristine_git_object: d15692141667a70c734901f70a0f7874748007ee + last_write_checksum: sha1:6a9bab6ba28f5b0b04f6343ddf478a3d0498f843 + pristine_git_object: 7d5a547c8168af9b4cfeba2fb9fa5c7b5c40a5a0 docs/models/createplanpriceintervalresponse.md: id: f872fc43537d - last_write_checksum: sha1:c4dcd437608e05903abbcaaa3b5436e89e2c3751 - pristine_git_object: 51398aaab5bd3094d490023e1a62bbc8f26752cd + last_write_checksum: sha1:79245427f2125d009533d4e932cb7fbf13040539 + pristine_git_object: 3b55edf3fd8d2f74378fa8f4a7d126fba60784e3 docs/models/createplanpriceitemintervalresponse.md: id: 24549a1388a1 - last_write_checksum: sha1:48389d49de582e1ce66bb73ac3a97e14f9d09421 - pristine_git_object: c1e2f83b4b2745109f05429dc564381e35566913 + last_write_checksum: sha1:2b92c1885222f908b45d789d0c35bc26f4f69fe1 + pristine_git_object: 505ffa29519c0299196f77893d21b1c03416ef1e docs/models/createplanpricerequest.md: id: f12e1c990ec1 last_write_checksum: sha1:78d33eb36c61df233aeed99741f184760683cca8 @@ -689,12 +709,12 @@ trackedFiles: pristine_git_object: fefa0c8a14e9b08d8317fc8d3733dbb42f1dca09 docs/models/createplanresetintervalrequest.md: id: 0cddef9265c3 - last_write_checksum: sha1:af71157c843c2697f551d58b091bbfce73b380b0 - pristine_git_object: 77e07403fee45b00512fc707de405b9000abf7dd + last_write_checksum: sha1:4bf315605d9c46fc8aeae11e89708c8013b0fc6b + pristine_git_object: b2afd01b4d40951ec598280850b0fd3ee874c956 docs/models/createplanresetintervalresponse.md: id: b17cd3a6cba7 - last_write_checksum: sha1:a7fc918adc597dd6a8091837d069ef23b783d966 - pristine_git_object: e23ed719cb9b9068ce42ba8db9a4256bb582faf3 + last_write_checksum: sha1:b2d389267bac50ef73d4cf4afa23752778aa245a + pristine_git_object: fde9015d34005ce408769d28469ff9bd824fff10 docs/models/createplanresetrequest.md: id: 4269e3fff10d last_write_checksum: sha1:81ba6a1a229a056e81142b59227d39db3a6b6f4f @@ -717,28 +737,28 @@ trackedFiles: pristine_git_object: 1036a2ba0f28e32f48985255aecfa3bb52a0f679 docs/models/createplanstatus.md: id: c67e48774d6f - last_write_checksum: sha1:0e73a49760bb7b7240413e82280e4bf14527e81f - pristine_git_object: 0a02b6243e4951579c5296f3b0de279a6734bb53 + last_write_checksum: sha1:c19eea42455c259881ea44f52e6329d11bc2b924 + pristine_git_object: 42bd2e4fcf5c2d89d65e13476f984c3ccf48f874 docs/models/createplantier.md: id: c582ff76d889 last_write_checksum: sha1:159c3f51cdcde0027f66b0dc8ee14e44a3400c39 pristine_git_object: 9cf6204964346d5215a14551c0cac01660077d29 docs/models/createplantierbehaviorrequest.md: id: 2a8236046547 - last_write_checksum: sha1:884fc740aad1e4d1929feac86e4f8b3984fd305b - pristine_git_object: da7dc3726e2780838412c24fc2d12bd92bbbb21e + last_write_checksum: sha1:8d6231303c39b89b6e22523bc59f340c51762a5d + pristine_git_object: ed54766809f122caf3b59dd08c1f424856dfcdbf docs/models/createplantierbehaviorresponse.md: id: 491d2246191a - last_write_checksum: sha1:6fb032a0cde153e82c9ac9770fcf90fc7b307275 - pristine_git_object: c20a82090b832368daed398af77dca93bf0c78fe + last_write_checksum: sha1:b193b869eaf1688500ecaafcd2bedd1bde2aebf9 + pristine_git_object: 7f8c20b4309c3e5c3169f8da82b30d7f6303ad9a docs/models/createplanto.md: id: 3833b33d43ec last_write_checksum: sha1:bcb546882173424d017121b0557a8ffeaa27ab55 pristine_git_object: 420a9bf4ba28c64093d17fc095b8af35fe9f0d8a docs/models/createplantype.md: id: d50ef0678a3b - last_write_checksum: sha1:a42c152f56d182a6561686883df41eb1530c3605 - pristine_git_object: e663cedb4dcf7df50c677067c84556d8da938492 + last_write_checksum: sha1:70cd59b183f2e587a037bc5485453286d1a32dce + pristine_git_object: 32d3faf1d3017535673cf37297dc291ae3bf19d9 docs/models/createreferralcodeglobals.md: id: 368eaec7a9b4 last_write_checksum: sha1:abfc0223ac79b4ce82dc286b7eaec9ee7003a10a @@ -761,8 +781,8 @@ trackedFiles: pristine_git_object: c6e96becb74aa8c2a46feabb115ee3a5e735e248 docs/models/customerbillingcontrols.md: id: 758d49afce18 - last_write_checksum: sha1:e67e19d12e17448784e78721acf3840df65858b4 - pristine_git_object: ca7bf388008cc62cafdae70d7a54ed07b4143430 + last_write_checksum: sha1:572699f4bfcc5fbdc163b02983aaf751c0ab2037 + pristine_git_object: caf99500eb9d21d73f870018ca43fca5e7c10bb5 docs/models/customercreditschema.md: id: 5d9998b21933 last_write_checksum: sha1:fcfbe3056729cff42c402b9619076db84ec6ffe3 @@ -777,12 +797,12 @@ trackedFiles: pristine_git_object: 5871d14cc4f423e29232741f509f84d2937d81da docs/models/customerdatabillingcontrols.md: id: 9a9f4fd47b8a - last_write_checksum: sha1:97a246baee322c664635c5bdbba0985df68a3f25 - pristine_git_object: 7edaea4795dd69bc57071089a71e6a911dfe84a5 + last_write_checksum: sha1:19d195343c43238015f387b0dcf153504f1e1fb2 + pristine_git_object: 8700212663dda7fba64d677c5f39cadda6b9c4cf docs/models/customerdatainterval.md: id: a79b8e0d599b - last_write_checksum: sha1:7edaa6ed15b9896fa1045791f15cdfa2c8ffb788 - pristine_git_object: fd98960fe70924ae085f3d9d4ffaf6715e1c5bd6 + last_write_checksum: sha1:f19b611ec3f7ebb76344457ab86412eaed888ad6 + pristine_git_object: 3356899bd9fb2ade4a84dc4dba3f5529f7a76501 docs/models/customerdatapurchaselimit.md: id: c415017a8c4d last_write_checksum: sha1:13ac00bcab77110548bcfacfc251d4b4972cd2b7 @@ -791,34 +811,42 @@ trackedFiles: id: efddd27334cc last_write_checksum: sha1:5e216993820eb4b9f994f6f5fd00d5dd36e1a2c2 pristine_git_object: bbc12b74a98a4afbad687b3ebf4d7d9c3486ce2d + docs/models/customerdatathresholdtype.md: + id: 9b731ba64207 + last_write_checksum: sha1:2723b21288c90712c180ed0c8f6a99fc04bcbe2e + pristine_git_object: 1fe92ece618b1ac10d97cc9d385c2a8a7343afb8 + docs/models/customerdatausagealert.md: + id: 5476e22ddcd6 + last_write_checksum: sha1:2c59cd3e3c7bcc5ac3e649fd0fe9f121af216e7c + pristine_git_object: 4706c3e4cf38b2b26f4c70d1e0781fc3d454cd2e docs/models/customerdisplay.md: id: 0afec61f7fd1 last_write_checksum: sha1:a3332a4b41b4be6018d42faa10f22f8082a327f1 pristine_git_object: 1ef71da1d5c2368ac687b9c2a6c24be24c3aa0e0 docs/models/customerdurationtype.md: id: dc88db3adc93 - last_write_checksum: sha1:e5274f776927c0d1ab9301bf2d03f61230af3420 - pristine_git_object: 4c8485d3ee0d1533e7b19e5c3b01a7363ed27322 + last_write_checksum: sha1:42f7f4090ec893457e1f58865a7499a419dfcf4c + pristine_git_object: 8816de7792fb2034f720195e14f1324cbe0e67ad docs/models/customereligibility.md: id: 6ad6cc047223 last_write_checksum: sha1:d51b278d8dfaf98858914d99e41aa1f8274c3e31 pristine_git_object: 3eab0b39b6e3bd249c42919a34d53518f03c8afe docs/models/customerenv.md: id: 714d5f271769 - last_write_checksum: sha1:baf1fd8c3368fd3602ef5feb2f97714e40a6ec00 - pristine_git_object: 795d79e59aeae504a77a2d3ca10217be880e23e3 + last_write_checksum: sha1:0339c99475930033694fa4b6d63c9d0266596379 + pristine_git_object: b5fddc48061d589c49c65ec0cd5b65c5a1891178 docs/models/customerfeature.md: id: e11a0acefe5f last_write_checksum: sha1:581d157d60440eb1422d1aeb94d8fb184be5982d pristine_git_object: 9649f4d473f68f78ef54b61170388b95fb903a94 docs/models/customerflagstype.md: id: 344522e4c604 - last_write_checksum: sha1:aa7f2fad97d746fa377eb63af23eee177efe47b9 - pristine_git_object: 3cda316c1c6606a7a3ca9969c9380ae88281a803 + last_write_checksum: sha1:fcf82df2a6d55566a69eb913184ce3377a90a654 + pristine_git_object: dd512b21e8be2a5ee7c70ac4a07cbebc03472631 docs/models/customerinterval.md: id: 97ea82daee70 - last_write_checksum: sha1:a163475d02069bd4dbfa4773eaa16ae8ff68cf02 - pristine_git_object: c420d3b5770d68e7bb334d69a916b6387f4e30b4 + last_write_checksum: sha1:f2e098f1e5fff90ea6c51786c9ac693f085e27cf + pristine_git_object: be72d787013fc0f4f5129786c74d70443cd2ec15 docs/models/customerpurchaselimit.md: id: ab270cf19799 last_write_checksum: sha1:a1564dc135f60dd28348428f3bdbfedac8315a8c @@ -829,16 +857,24 @@ trackedFiles: pristine_git_object: 0c4e2be60011b34987f51b9633c725fa2ab43812 docs/models/customerstatus.md: id: b4c4d15740d6 - last_write_checksum: sha1:3550b095a17cbe64c6bb0ba3d38848aac237a8af - pristine_git_object: d60e52acccc16a85833f8d5eaac188951ec8067f + last_write_checksum: sha1:d953eab2afc602b6b65d6e4d8a04ef689b2dd4c1 + pristine_git_object: cec44f4bba76366778d4b14facff277aa85ed911 + docs/models/customerthresholdtype.md: + id: d6d10aecbd62 + last_write_checksum: sha1:fa35d55f76b2564e200184d9bf933650f989456a + pristine_git_object: f78e407be5c7b5a9caeac538daea37d5f090ea78 + docs/models/customerusagealert.md: + id: 8a97d4d05384 + last_write_checksum: sha1:1540f5d2394b242694406e174a608d3409e310b2 + pristine_git_object: 5a7fb04c7601dafc205736b9a962e2ecfa02d7ba docs/models/deletebalanceglobals.md: id: 14fe0d876109 last_write_checksum: sha1:6cbb33a21e005f38ae518bdda4a3aaed87173b86 pristine_git_object: ca300d4f009ccfd44c0b0d8f995d6e0408d3eda6 docs/models/deletebalanceinterval.md: id: 781b7acbeb0f - last_write_checksum: sha1:a22e41d059876a8f9134a41ff2d0dfd0cc87c064 - pristine_git_object: 0acef4bd9acb2592b3342e386221b62b3a490981 + last_write_checksum: sha1:6ffd513f62bc7f95040951ac3ef602bd78ec7d98 + pristine_git_object: 89b9186dd3c2ccfe67fbbee3ce15a34fc7a664da docs/models/deletebalanceparams.md: id: 0bd1b6820267 last_write_checksum: sha1:6648fef91cb71e3064c49158b945ef26819eac11 @@ -905,8 +941,8 @@ trackedFiles: pristine_git_object: 13571b91f635c237d1c0e67aa3a39f2b0542b9d9 docs/models/entityenv.md: id: 5c026cde1184 - last_write_checksum: sha1:18b6be25de69c48ced637a6f6af1c220c472f8ef - pristine_git_object: 3c6a8b29b6df7ff5445d964e3fcb32bd7fbce60e + last_write_checksum: sha1:8a7e9be1424504f599c97ab269154498bc9c498a + pristine_git_object: 4791ce7334f87c2857cfc98e758fa7623cbed062 docs/models/eventsaggregateparams.md: id: d82804f4b722 last_write_checksum: sha1:ead9eb6aed3789a3c7176285f6e8f6b38be58324 @@ -917,12 +953,12 @@ trackedFiles: pristine_git_object: 81ab707fd50986600be0a0bc1cf4f8af1e6b6eb9 docs/models/expirydurationtype.md: id: 7ba992bf53cc - last_write_checksum: sha1:368f1d47012462b1ce9bd136fd0aa4a88cf419fb - pristine_git_object: 675c956152029541c6bfe8a49519e6ba21a95712 + last_write_checksum: sha1:621bdda28fc13c065067c82524420eaa28628bdd + pristine_git_object: 80366277f5fd22322c89d13a38562877f09df7e8 docs/models/featuretype.md: id: 9fd6a7ea7659 - last_write_checksum: sha1:a21f7ba709ab7f1cd5a2d83755064445d8b2c85a - pristine_git_object: 05ff42a520e8804d3d219809d835a13f5ed78bf5 + last_write_checksum: sha1:e78cc7c19a8c81fe196161343bfa5a508572a13b + pristine_git_object: b81adbcda5cd11189273956e2702422c649aa141 docs/models/finalizebalanceparams.md: id: 610af9b4049c last_write_checksum: sha1:9f6c1e1ac2c7c0670ee998fe33e2f633edfe5c0b @@ -949,24 +985,24 @@ trackedFiles: pristine_git_object: 7da619a9e958f42269a6a8c54394f6eb6fa390c3 docs/models/flagtype.md: id: aa8e2212e678 - last_write_checksum: sha1:5938613e76e9a428845bae6670d23526ffdb6245 - pristine_git_object: b4825143b8c900e99b90deb32551f61502cf3b5d + last_write_checksum: sha1:dbe645cdcbe5a43eb811fb5fee6030491747c911 + pristine_git_object: 64f21e86a3546517e7bcc622fa280367f538584a docs/models/freetrial.md: id: dc73eb37daef last_write_checksum: sha1:0f8ee227ca42edfb64780b836e99518a340e3d82 pristine_git_object: 589d1bb1e1cd47c62a789a8cff85528280df7e94 docs/models/freetrialduration.md: id: afd918f70308 - last_write_checksum: sha1:858ad3ce1678634743a1c702062787acadcda18b - pristine_git_object: b98731904496a288b0e3b0ac3574b5d26863f374 + last_write_checksum: sha1:55685a177c7e93c6a9806a7e7d0aa006276d9ac6 + pristine_git_object: 94300d74e7fc30cbf84fd822357b2fa2b60d3d0d docs/models/freetrialrequest.md: id: b3ecac09ef21 last_write_checksum: sha1:169d32a309bd509f208a93cbd32c494a5fb86040 pristine_git_object: 043bcd6a83e917e5245a2490db35ecf15c75e697 docs/models/getentitybillingcontrols.md: id: 4a2d7e54760e - last_write_checksum: sha1:ad9f93b779b330f3036469acdb15e9d6ecefa3f2 - pristine_git_object: f4e72b344aa1eb69b52854c1dbf10a919d2f703c + last_write_checksum: sha1:12fb71935e7acc7e4bdf5b5b4ecc5f26e29bbb45 + pristine_git_object: 846d3b5769ff7ece10edc2b6de7bf9c5b387158b docs/models/getentitycreditschema.md: id: 887b3eaf1c97 last_write_checksum: sha1:e0997dfddd5fdeeaa70773ed26d214132f097329 @@ -977,8 +1013,8 @@ trackedFiles: pristine_git_object: 2a77af15d5dff4f6f3f7ffd678ee41f101828ab0 docs/models/getentityenv.md: id: 446f208bb477 - last_write_checksum: sha1:f7b1e6834f0af7844263a35c619651a4cd69cb28 - pristine_git_object: 829882d267976886364436e4844c2efb99a2fb26 + last_write_checksum: sha1:5c573af95d0443415d84736118217658388bc1bd + pristine_git_object: 63a901b4fbfb4df8bd9ecaa0da5c1a9c7ac69418 docs/models/getentityfeature.md: id: 70f61caa19f2 last_write_checksum: sha1:6147805cbc0333d17b23cdbada931979c1d92e5e @@ -1013,16 +1049,24 @@ trackedFiles: pristine_git_object: a92ff6b9626913630cc43f3e3c9ea4d2db67d33f docs/models/getentitystatus.md: id: a9dc0fe0776b - last_write_checksum: sha1:c0084ca6aa06f12532d4d2a5d4389ed2c395b899 - pristine_git_object: f823deba8a676305bfe817193613486b58c6fce3 + last_write_checksum: sha1:0ef801e1509cec493b2185893c7ff7a6a8f071a9 + pristine_git_object: 9c9b61b2e49731c3618f2bba373b8c104d75e035 docs/models/getentitysubscription.md: id: 79bea398e954 last_write_checksum: sha1:8713b786fca38066c8a961fae76344a314db8d10 pristine_git_object: f07e2baae2a663c3d3e59b936f707e65bc5722d2 + docs/models/getentitythresholdtype.md: + id: dc124a35cc1d + last_write_checksum: sha1:017282736309684116699693c2c8df8f8a2e1c63 + pristine_git_object: abfefb9b4cb6b8351265e08253b154082ededfa4 docs/models/getentitytype.md: id: 18fa9d2e3acf - last_write_checksum: sha1:815cb6238ce71a6e2aa917ffa4962115e9ae501c - pristine_git_object: 7e2b494a58a6995cc91f8eb4587df9825bcef0f4 + last_write_checksum: sha1:5908ff1d81efbedb87596d2accb4883fe98ea4c8 + pristine_git_object: 48c3e34490024bdbcd60298e81e8362f6a3dbc51 + docs/models/getentityusagealert.md: + id: 0f1780b25c03 + last_write_checksum: sha1:d133be816f6deffbe6af091606cca271f126ac8c + pristine_git_object: 0ff5f721cc5ed51a37f7d7491b92e4ff2f5bfb13 docs/models/getfeaturecreditschema.md: id: ba9cb5483630 last_write_checksum: sha1:449f708f5ad09a53efad1ec07b65c0889a0b74ec @@ -1045,24 +1089,24 @@ trackedFiles: pristine_git_object: 517a8f8299a7fd6c192d7a0e1ee842ea6aacc804 docs/models/getfeaturetype.md: id: c54f4a256601 - last_write_checksum: sha1:b4139d96a66706eab122e9505e68fc158cb1a4c0 - pristine_git_object: b8894ad0c26ca43ce443ef4678ef6ebe6bbf9a8e + last_write_checksum: sha1:150f3eaf62699c8c37779ef9a445e52da3b4c9d4 + pristine_git_object: 68e731979e397111d285711a13691b49569cb515 docs/models/getorcreatecustomerautotopup.md: id: 2beff692c107 last_write_checksum: sha1:f9dbd5ab95ec0d4f0d86c3a2efe45782381488a8 pristine_git_object: 40ed50ae3d7019b658cd172a18723b61c096fad1 docs/models/getorcreatecustomerbillingcontrols.md: id: c863934c5751 - last_write_checksum: sha1:a8e666fdd1231039a4a57aee535f58944a4428f4 - pristine_git_object: 6d4136daef570c12ce721857821b9bb60157c118 + last_write_checksum: sha1:d1a6883a0191fb4e2ba75205dbdd639239cd00ea + pristine_git_object: ea80bafde22c0aa16e17ac4e4b3975280348e378 docs/models/getorcreatecustomerglobals.md: id: 1cab932654ad last_write_checksum: sha1:a98b6f0751961493a875cad885360ca4ab817955 pristine_git_object: 4e9384d4fa1e76e5f7072105ad37bd960b3aedfc docs/models/getorcreatecustomerinterval.md: id: bf93ecce87e0 - last_write_checksum: sha1:69c9049fea350c4aba58327a2cae94907470ef1a - pristine_git_object: 40ddcf6f88ed6fe3b00a09567e5e8f6a43a75e83 + last_write_checksum: sha1:a81b370c7073d5fba80652e4b6c55ddc182aacb0 + pristine_git_object: dd40392108052ed1300f552af9be3cad6dcebb89 docs/models/getorcreatecustomerparams.md: id: 6bb57e046821 last_write_checksum: sha1:ab5566304b1ac2b2045c9404f3b88ffdbfd38c84 @@ -1075,14 +1119,22 @@ trackedFiles: id: 1e9f20940ce9 last_write_checksum: sha1:0fad134904747354b93ba04bc4954dee8bbcbc23 pristine_git_object: ec08c25cedd9946144cde10d6ee95fa648e1095d + docs/models/getorcreatecustomerthresholdtype.md: + id: 20bb7fc3fcdb + last_write_checksum: sha1:84458f0c37931c8e022b12313f6d81b4c5f9958b + pristine_git_object: ac3fc92c583a43b07c0fb7697b6e4e6d80ec9d4b + docs/models/getorcreatecustomerusagealert.md: + id: 3b9d65693d28 + last_write_checksum: sha1:3240ed593eefdca0db5bf576bd396b2d0017fc6a + pristine_git_object: 122fe453fb8bd94bfffd63cbb8490a945235b883 docs/models/getplanattachaction.md: id: 0ce3131b10b7 - last_write_checksum: sha1:16ba837ca31d496b44863d2438f68d6fac18f397 - pristine_git_object: 3b8852a3d63ae0f3171ba54282c196eab871caa3 + last_write_checksum: sha1:6b469e1d017b1de218433d30100c0e843848d7a8 + pristine_git_object: 222e0a3893aa564e52943137e83669331ac037be docs/models/getplanbillingmethod.md: id: 5899381c0e82 - last_write_checksum: sha1:9762f5d1ab3cbd4d570c1b403171d3b7f3df6128 - pristine_git_object: 444e142954037161bd4aa63e788a71f75067a871 + last_write_checksum: sha1:93d35c83c757a6f8cc683ac6bbd7f84892568f37 + pristine_git_object: d8b089d97ce1e4230dcff81169bfca45d7f7fa36 docs/models/getplancreditschema.md: id: f70b7a9fd404 last_write_checksum: sha1:1ad49c99400c9f9c735bf18686e2c7f3a0715803 @@ -1093,16 +1145,16 @@ trackedFiles: pristine_git_object: 55ddaf7c1fd4b81efcfd565d217596cd3cfb7ba0 docs/models/getplandurationtype.md: id: 531b497ea20b - last_write_checksum: sha1:6367aa97323f629634099fb9d45375ba329e1c43 - pristine_git_object: 90909b13f3bc4b014c3993d84085b805a5ea6db6 + last_write_checksum: sha1:df5c7a55a41ba415e20ad5380cec7bfc25ac29ac + pristine_git_object: 5f29921945accc78755b28a5e69eb3e4a68acd18 docs/models/getplanenv.md: id: f83aac7b06d9 - last_write_checksum: sha1:06675b31e96de0c8f15d8b6eb89ab182be92cf36 - pristine_git_object: ca505312f8bc091467e81b6905b716d611d33225 + last_write_checksum: sha1:9a144c5d18e790a681bc72ccc8dc7dff6604d21a + pristine_git_object: b8d4c39f8242f3bd334456658defdde380e31d77 docs/models/getplanexpirydurationtype.md: id: 03a36cc18dfa - last_write_checksum: sha1:1ca4877e84edeafb35dc4bb81db5f19b9499f662 - pristine_git_object: 0a11d2d6659ca9b3962dcd4c245030a57e2b378a + last_write_checksum: sha1:8fbcd41010d7b858f04b035389387133fdfaaf20 + pristine_git_object: d127699651820a18616b4c971dd9488e96c07d3c docs/models/getplanfeature.md: id: c4414dd51a12 last_write_checksum: sha1:0f89828767697ba0086bcce9cbe0024876ae2dd2 @@ -1145,20 +1197,20 @@ trackedFiles: pristine_git_object: bcbad67a252e45ad1181c76834ea98b725ac56ec docs/models/getplanpriceinterval.md: id: 49e240107fc3 - last_write_checksum: sha1:9f664db6fef8de9266e28fc313a3ad87ce29f846 - pristine_git_object: 0fbbbe940a7d7be5272478f46c4c1fdc0a2c686e + last_write_checksum: sha1:46f284ab72a58f462848a006ea9dc26d944c59ce + pristine_git_object: 3370145b996fed00a85041e7c500c0271627c930 docs/models/getplanpriceiteminterval.md: id: f5a725b2b8d5 - last_write_checksum: sha1:02770e6af2a6ef10b45a4675e52f43d1db263067 - pristine_git_object: d70ae03da2d27de9c4932d44228dcad913386170 + last_write_checksum: sha1:b68a8c4fbaba805bc18f77116cdcb8776c267c05 + pristine_git_object: 4da7249fe8ada0c78fdbff11f1d2e57e9edf15c9 docs/models/getplanreset.md: id: 0d4683eac10d last_write_checksum: sha1:014167206a4a206315d9530426cd4d21903fbe90 pristine_git_object: 2ad13f83a372f25f8322245dc743e9c78114097c docs/models/getplanresetinterval.md: id: d518a38e6124 - last_write_checksum: sha1:2c4620e76372b625802d55c3188532ecc7a7f2ad - pristine_git_object: 893f1399489630534ab838e9507aa9ee95d98633 + last_write_checksum: sha1:1461a586f2956642f3ef7fb76697805710cc106e + pristine_git_object: f89ea0c6427dda0e3791eeba61089a4bb4f2a3d6 docs/models/getplanresponse.md: id: 2b0a620fdbb6 last_write_checksum: sha1:b3bc1ce22f83a8b78e3bcbd75e364c5615974584 @@ -1169,24 +1221,24 @@ trackedFiles: pristine_git_object: d6778e26ece0dd751384156f2417e3a87b435f7b docs/models/getplanstatus.md: id: e934416f9b0a - last_write_checksum: sha1:b366acc3392ec751e92a8e07dec9074a03a98297 - pristine_git_object: 8b1eb7d78f8b265ab13e1822740124794e4e4ff3 + last_write_checksum: sha1:c463eaca5076258ce803f4dde67e5872786893d9 + pristine_git_object: d6f47e9f63a25482f46524554d8fe0b43c09fafd docs/models/getplantierbehavior.md: id: 43d70b082849 - last_write_checksum: sha1:498f80b2248b63d64e57495ab64dd7c82941f8e3 - pristine_git_object: 512006d976e36f5a5bd72fb4520bbaf7b1696747 + last_write_checksum: sha1:8d33409091d654dfbe7d0cdb495289b363e3da7f + pristine_git_object: 74bac9d492b97f798091959496f6301daf1b8fe7 docs/models/getplantype.md: id: afbdf26f0f4b - last_write_checksum: sha1:6d5dfd04c5d95c235f56eb23e31f497c9ed91247 - pristine_git_object: 7390b496108568e19aba42d36c5c3b508e3d42b3 + last_write_checksum: sha1:7b11bca3ac08a5bd951768af39bc5a2d2867a1ac + pristine_git_object: 64f5d61046d023c2293fa50994151408e0ddcd06 docs/models/includedusage.md: id: e844c6f90fe1 last_write_checksum: sha1:f45eadc2eb0f9f2543fcfd3b0d671c8fc9e1c5a8 pristine_git_object: 6af710e8393ec0d5bd29b7bc2969174b69548d55 docs/models/intent.md: id: a4a6fa8818f7 - last_write_checksum: sha1:9079fedc03e6b5c3469c752774a972f27bdab857 - pristine_git_object: 2fb90b98293f058b3624f39b89a91915ddb73ba6 + last_write_checksum: sha1:7a2ffb6b28f1f53a993132b1d38e527ca4472d95 + pristine_git_object: 41e20497ed5f6838daa1c3b689bfdce018a5758f docs/models/internal/globals.md: id: 9c173b87f41f last_write_checksum: sha1:f1b8e7ce642026cd3ca1df79e67c7a011f487fca @@ -1209,8 +1261,8 @@ trackedFiles: pristine_git_object: c83159c6aebb5837df31c3af20a6dc463851f26b docs/models/listcustomersbillingcontrols.md: id: 7bf70880a06a - last_write_checksum: sha1:9a777a711c543fdc6093250fca24df1410356296 - pristine_git_object: d3e560e51af656c502e922155cb1ee6a1a4fdac0 + last_write_checksum: sha1:162f377e954d09d4151abc63360c9ab16c1d4499 + pristine_git_object: 14a51d6949a530cfbcb9b4f0c6ba2ee88eea7e48 docs/models/listcustomerscreditschema.md: id: 233b13b5c3ec last_write_checksum: sha1:cd18a266307472b08dc8f8bfe120d2134ef24577 @@ -1221,8 +1273,8 @@ trackedFiles: pristine_git_object: b63a253ef31d05f492f054307ec26cef6af31a36 docs/models/listcustomersenv.md: id: 4a356fa1f33b - last_write_checksum: sha1:f36e4a5d525b7e69e2dd2721b25b4451f272d383 - pristine_git_object: 9c87d7fb576ddebfd331368c52799c7f92eee799 + last_write_checksum: sha1:e883c9c38a73b9910ff2cbdd6adef00b226ff214 + pristine_git_object: 750e1fe57b24381eafec336a5d7d1a97980447b8 docs/models/listcustomersfeature.md: id: 0d1d6a29245e last_write_checksum: sha1:c2bc21d88712f18bbd4fbc5fa7f2085045a379b2 @@ -1237,8 +1289,8 @@ trackedFiles: pristine_git_object: 0c99ff068e87c00c04403bafdb526327c872e28d docs/models/listcustomersinterval.md: id: d395228928af - last_write_checksum: sha1:3ee69e14dabd31a795eb88fecbff0d52b0a6007d - pristine_git_object: d059fe1ed2f68a59ab081f0937d3629ba9135532 + last_write_checksum: sha1:7aee8978d9fb061b53cc3e5f028c5c9164864942 + pristine_git_object: 77c3c316e986ee99faee7c9fe10596e1b13d745a docs/models/listcustomerslist.md: id: d71dda8a696c last_write_checksum: sha1:ee8b36fbcaf8b08326a04eeb383c71e3bc10c1ad @@ -1269,16 +1321,24 @@ trackedFiles: pristine_git_object: e3b685690aa916dbd951480743e4ac07ef7d8834 docs/models/listcustomersstatus.md: id: ef9a6bf44535 - last_write_checksum: sha1:c55467eb7401c28e4bc671df976ae4e5672f5461 - pristine_git_object: a022d4645d783687e38950dbd3553f183027d264 + last_write_checksum: sha1:956f1e484cadbb76fe9bf2069fa624885e5dd2e3 + pristine_git_object: e11dcb9eae8b5df39a7f50c909eaf673d40984b4 docs/models/listcustomerssubscription.md: id: 563487abda41 last_write_checksum: sha1:fb3b140e7a41d14e1561e82043698326a31be834 pristine_git_object: f2257cb7235a82b97ba6a773a19a81345252e112 + docs/models/listcustomersthresholdtype.md: + id: 891ad14c54c6 + last_write_checksum: sha1:9405e2dfdd934a185fca4ced935dd0cdb8d252e4 + pristine_git_object: 90e2f92efebb8266b4d626a6ba266718ad13409f docs/models/listcustomerstype.md: id: 431485f373da - last_write_checksum: sha1:38c1bf892ba103e25ee8c0bda7b322c7b1c97150 - pristine_git_object: 408f48dfb01ee2b3f69e4d0486f3f6d5ef3626f5 + last_write_checksum: sha1:161036bc83c3eeea7c736af588e8523330cf7f30 + pristine_git_object: c045d3f1352a590fe35af661c5343ed9489e7347 + docs/models/listcustomersusagealert.md: + id: 3f57d100be4d + last_write_checksum: sha1:d98c87e661b0cade8fc6d6e09c67323dc6746f3a + pristine_git_object: 69f720b7645a59634c4d66c2b86a432bc1ffe424 docs/models/listeventscustomrange.md: id: 7fe97412d600 last_write_checksum: sha1:d9fe1ecd94b4360de822647bfc6d10319a6a11de @@ -1329,16 +1389,16 @@ trackedFiles: pristine_git_object: 6e6cfa9219fa9f0b24adc850386f46f8b6fbb0a3 docs/models/listfeaturestype.md: id: 38d27c59e570 - last_write_checksum: sha1:456db91e3466968ec933e354505445e480244a94 - pristine_git_object: 1d0e84005191feeaf5423baf5c29ebbe47f149ea + last_write_checksum: sha1:f53f04cb15375a2b0f35914aae8492ac09017fbd + pristine_git_object: fbbecc4872d8313dfb7097181436b0459abbb29e docs/models/listplansattachaction.md: id: ebea6c110ad3 - last_write_checksum: sha1:5a53c97c8e2f16407c174de56d2a17a1897d3fff - pristine_git_object: b99081a2408748dae1cff55e1fb7442ab5afa796 + last_write_checksum: sha1:6b8bc81c4558884f94437c679091829f6044f67b + pristine_git_object: bea25c4880ccfb2ab728c9500f018983ce9d5b80 docs/models/listplansbillingmethod.md: id: e3bc081b1871 - last_write_checksum: sha1:fe30367f3d3dc7caa417ffb1cb7eb41a233892be - pristine_git_object: 0a311a1a9fbd103a9796c9c96b056b48a17f593b + last_write_checksum: sha1:6dbd9106af5281ed0050a6079d13ac94a3be55c5 + pristine_git_object: fbfdc8106d7c545f591ccb1580698953c01bfd6a docs/models/listplanscreditschema.md: id: 8f4524699ea8 last_write_checksum: sha1:8264d803e53a657b5077baf27f79e86b9aca6c1d @@ -1349,16 +1409,16 @@ trackedFiles: pristine_git_object: 1a23815de5ab376a2f153cca506038f63181188d docs/models/listplansdurationtype.md: id: 9495db2c46d7 - last_write_checksum: sha1:6c48c9378be8d9a0b6bd38fabebf07742a149d42 - pristine_git_object: 90d99ae19a664379f001a33050a424b0958b9626 + last_write_checksum: sha1:0d6fd83d4998c2a27143e76d4b1b4126e4038822 + pristine_git_object: d717ad36f08313dd30dfacf50f6c006c70eb3141 docs/models/listplansenv.md: id: 31607dbe25dc - last_write_checksum: sha1:c62abd91d1c56eef136ed56af29175a7191ba25a - pristine_git_object: 4a8cffd6bfccbaa793894297baf9a5fe7eee5e32 + last_write_checksum: sha1:9830f7348ee862987f13efc4a01eb6a6796ede7a + pristine_git_object: 02c29dfc73212a614b5c8ec44f720bae159e389d docs/models/listplansexpirydurationtype.md: id: 95a054c2e519 - last_write_checksum: sha1:38648b272697d05f9eb685b1a8fccf76b1c8e4d9 - pristine_git_object: ed20a3beba9ff8fc24604939bf7e23a0c54d77cf + last_write_checksum: sha1:584ed1565d5e0e1526c1972a6f260a75b57b6635 + pristine_git_object: 1a2b7ca1f74cbb6ba3decaf7c4589617d3f530f0 docs/models/listplansfeature.md: id: 695e022227fd last_write_checksum: sha1:be788a1a590651cc99265529af796851f8295b7a @@ -1405,20 +1465,20 @@ trackedFiles: pristine_git_object: 183984624f5db2fa0531d6315e3a0992bc4c5bb2 docs/models/listplanspriceinterval.md: id: c6eed8ecd3ab - last_write_checksum: sha1:e0615391bccea8d12f365cd0b02c49656e215973 - pristine_git_object: d29ae18613bb21f1aeeb0140cc0e9b7217f76603 + last_write_checksum: sha1:a406660a86cea1ca01551fde8bc67a6692a79322 + pristine_git_object: 2a8a39f8ac58a6ab975be8c95656e259b4acba31 docs/models/listplanspriceiteminterval.md: id: 01ecfb4227c3 - last_write_checksum: sha1:cda215b4295d09c14413994ee52d9717ecfeb3c5 - pristine_git_object: 8f9e7ee9c4b285e26bff7fdfade3749dc9feb5bc + last_write_checksum: sha1:43ad14ac0b516caa6d79c4754be679cbeb13eeca + pristine_git_object: 9f629477abfff58f81bc131cc219559bb0696f83 docs/models/listplansreset.md: id: a094b1e8ad88 last_write_checksum: sha1:f393e47631d54b7ca392f89b6b8dd298044cafba pristine_git_object: d2d1fc44a5a802750cf3579fbe14acfa81d62705 docs/models/listplansresetinterval.md: id: d3e52a6a7150 - last_write_checksum: sha1:4181c23ae65d01de9ca04613fffb312efff27761 - pristine_git_object: fca8329375e116c297560104caa66e1ad0ddcae3 + last_write_checksum: sha1:0cc22e923ec5a7af4e9c5a34accc5be12d97a1ce + pristine_git_object: 3801f20f4a7442df7a92c79cb4391189da06b9a7 docs/models/listplansresponse.md: id: 36b6e3fa6daa last_write_checksum: sha1:9763e11551ddedad472de9cbce9e6751752265bc @@ -1429,16 +1489,16 @@ trackedFiles: pristine_git_object: b1465a838739806bf638ee8507880d90d397724b docs/models/listplansstatus.md: id: 0e36930afc34 - last_write_checksum: sha1:9d45881741f8a142247a4006d141aba297608c27 - pristine_git_object: 128b5ab6310da37fb03fe81aaf1af862329cea33 + last_write_checksum: sha1:e3b3e382985eba9756c25c1fe2290957e3652e67 + pristine_git_object: b8d6dc18c9d4545db0e3f36685f5fe71f5d67590 docs/models/listplanstierbehavior.md: id: f910370a1711 - last_write_checksum: sha1:6f4e9ec42f871e049b0b2932b24f1c148ff42d64 - pristine_git_object: c88a32bdd79e74b7a2f348248139d6b3f32df974 + last_write_checksum: sha1:cc7e6216d011b75eebfa959dd7a4c88422c9eae8 + pristine_git_object: 50649d135c09691831f63fe930cbd678240512a1 docs/models/listplanstype.md: id: c265fc1d3752 - last_write_checksum: sha1:ee4d791c463a7a530becba66e8f576b75b7ba217 - pristine_git_object: a105ee9d7d196ff96d7d56e990860f75cde637f3 + last_write_checksum: sha1:fee41af7544896b624de7a39fd20dadcf73d8dad + pristine_git_object: f5cdaa931dfd1225fc6e7e45b4c7df54934a462d docs/models/multiattachattachdiscount.md: id: 84b9e0a4d680 last_write_checksum: sha1:7f6655964faa1676f26c26520d23e42cd2eb8af6 @@ -1449,32 +1509,32 @@ trackedFiles: pristine_git_object: 290b9413b26177294f70e9beaadedcc9981cf6dc docs/models/multiattachbillingcontrols.md: id: 1b5ff234d470 - last_write_checksum: sha1:75ed3e4ae35ede58406e40e5142c80ea174dbdd5 - pristine_git_object: 325203724a8e3e6588e24fd6ceaeabfb7f2469f2 + last_write_checksum: sha1:f5a092b36b63401afd1d0cc3aff81d43421503f6 + pristine_git_object: 4647d12f7799a98561f249a38ff6d4f0772ab4aa docs/models/multiattachbillingmethod.md: id: 78bd00e832a8 - last_write_checksum: sha1:b71d8bbe717bfb4a1807f569ada8f10d6411cc6e - pristine_git_object: f77600fa9e3c31833d7e0070240eedeefca91039 + last_write_checksum: sha1:6b3e4bd84e01d8e880a8be15caa90484840cf064 + pristine_git_object: f7af35a930bb6948739c84aaa55b6793fd4056fd docs/models/multiattachcode.md: id: f423dbc408f7 - last_write_checksum: sha1:2f92a6cc4f83c35e4b26d376dc24494f9a717d57 - pristine_git_object: bbd3919960c0184383fbb4bda321f931fa81c0ba + last_write_checksum: sha1:f0a120e4cd18acd40974c037f768ce9e73b79fc3 + pristine_git_object: 47ba65d1e0c495fedda0f6d28fb79aaea9f89a3f docs/models/multiattachcustomize.md: id: 5f3ca0c4e8cd last_write_checksum: sha1:45fd9bec1e1233c2c7ae11ccc4fae5619dd06ec1 pristine_git_object: 295b2878cf8db291260aaac31fa5535b10661a27 docs/models/multiattachdurationtype.md: id: f5a45a0c8df0 - last_write_checksum: sha1:c5f27a80b42ce7a2a8c3f77b91061ad4795a9077 - pristine_git_object: a4948aaf9a86a6b83c4ebdd8633cf98dfce5b2a5 + last_write_checksum: sha1:0ac6362a2845051fff61ed4e77f7d711c689f575 + pristine_git_object: e81aa4ad0a612e36071ce045ca46d84b6d3dfc84 docs/models/multiattachentitydata.md: id: 59bc1e3bc662 last_write_checksum: sha1:0f7dd9929296d0c0d178e4220c05626b9eeb150b pristine_git_object: 44822b7ce7c29f2849b431630ed6cbe5240fb7eb docs/models/multiattachexpirydurationtype.md: id: 5515efd998cf - last_write_checksum: sha1:bfe29b5da73e5a32d8573c20382b14caa103d39e - pristine_git_object: fcfa7e53441dcb2503a93af52566ed34b410e9b1 + last_write_checksum: sha1:9d197dfe50cd37d043f9416f80072fa479053528 + pristine_git_object: 1a4762ca683493ac539f74fa6f1809c2aa121bd5 docs/models/multiattachfeaturequantity.md: id: 41d1fc07ea2b last_write_checksum: sha1:4e17966501c6555f7b28de4bdaf6e0a8d30d877b @@ -1497,16 +1557,16 @@ trackedFiles: pristine_git_object: ffc930cdd73dafe972deb77b4b8090cdee001cdb docs/models/multiattachitempriceinterval.md: id: 0030b6692fe1 - last_write_checksum: sha1:8ab49b031c75684ab2980f52e04ea8cc77978984 - pristine_git_object: 96afcc6bd84e6e1a1f94d698f806cdffeb56c41e + last_write_checksum: sha1:02e35cabef42fcd6e113495f3b860ce35eb572ea + pristine_git_object: 046a3f693366599cccd226ec67465e4de00077ee docs/models/multiattachondecrease.md: id: eb882098e74f - last_write_checksum: sha1:bdd219f50cad5e7bda7ef518ac765e75e8537d66 - pristine_git_object: 079850e4a5fdc18e3dd4d65b35bb40e91e49fbc9 + last_write_checksum: sha1:1825b557a75a40639c1b329d849a5ee50d0448c7 + pristine_git_object: 5dd8abe5f6fab10dd5b5fd0e1219c277fd52f49c docs/models/multiattachonincrease.md: id: 2645fe9f0f62 - last_write_checksum: sha1:e2e385491d1b02903c079e71f1b7922e8c6bcae6 - pristine_git_object: f2692e537169b9b000384c7749a5b9e9f6e3df2e + last_write_checksum: sha1:feaa98c20154520deefe1c89929f33b6365670c3 + pristine_git_object: ed7d58da3b22ac78c0a451435246bb87175e0c0b docs/models/multiattachparams.md: id: afacc821df4d last_write_checksum: sha1:ecc7a7de5997f0045534a255e263ac2a1d8e72d2 @@ -1525,16 +1585,16 @@ trackedFiles: pristine_git_object: f5035ac6fda1d63df18f9a22fdd0f565e9778627 docs/models/multiattachpriceinterval.md: id: 58fbae5e506d - last_write_checksum: sha1:573cc1cfafc85af26cd3bbf3143fac03a626b8d7 - pristine_git_object: 3409f9610bf4986b7ac4c71446801dcdcfecb449 + last_write_checksum: sha1:525bb8214350d1f4f120a381b8a36b32a3a3698a + pristine_git_object: 7bb54d92dd1dc404dccd12e692d2aa4c13c72290 docs/models/multiattachproration.md: id: a4ccd95a4f1f last_write_checksum: sha1:644594ce2e14ce35546f83ae9e185a066b359294 pristine_git_object: bdd561e1f8db63ed4a65fb25ac0bc8514ba94ecd docs/models/multiattachredirectmode.md: id: 6898b471cd23 - last_write_checksum: sha1:4b14153be4dcb9839614f549b0094da779ba2671 - pristine_git_object: ff721d72fdfdea39dbdf5b24629c2971718edcf0 + last_write_checksum: sha1:2e43963cb0c8ea821f8a78e1b030a68997bc0ff6 + pristine_git_object: 5012c8235c34d8d36b80a929ea9bd0ee4137896f docs/models/multiattachrequiredaction.md: id: d0c8ce104e28 last_write_checksum: sha1:b39367dbf7750b87fc30c1b5f90dc6a2ce5bd1cf @@ -1545,8 +1605,8 @@ trackedFiles: pristine_git_object: d10f501c27db623587ad477725f490326dd130ab docs/models/multiattachresetinterval.md: id: bc4b71af1ebb - last_write_checksum: sha1:38f6bd86601fbf385f79dc581d94b85bc052219d - pristine_git_object: 129f668a7e45b700cbf425f8a3807f384a01aa86 + last_write_checksum: sha1:cafeb533593f4635c3f18c941c88d6c9b884e6c8 + pristine_git_object: 34e299ea1a1a314bb95fc779778788fdebdd2eff docs/models/multiattachresponse.md: id: d919bee4ec6b last_write_checksum: sha1:7095fc0913192166bc2787e44664fa2cb48b038f @@ -1559,18 +1619,26 @@ trackedFiles: id: 88e2737dde1c last_write_checksum: sha1:e8c44943dc2d60ddbdd49a637cca46662b69b859 pristine_git_object: a471f442d7114497bd26bc26c10c594a8a247ca6 + docs/models/multiattachthresholdtype.md: + id: bf10e2aecdf0 + last_write_checksum: sha1:c5a4e24ac9bec396ec2f7ec0ff474caa1b2474d8 + pristine_git_object: e41887ca18f8a360e3f0e3d63b48863397c63a3d docs/models/multiattachtier.md: id: 8ffc11d3087a last_write_checksum: sha1:971921079fda10ca0dd9cdfd56e5ceb849c43ef5 pristine_git_object: 1a8295d39066a03867699c932ac0b9c8d296bb41 docs/models/multiattachtierbehavior.md: id: f61b0b7ebb2b - last_write_checksum: sha1:de3c8c01c2fb0c3b2dfe2425309608e93a45a671 - pristine_git_object: a3e5baedd30aab6f13641a6615af21c65fb8b1ad + last_write_checksum: sha1:360f87ccc7ab2f4d2190eed9159b43042fa22bf1 + pristine_git_object: a769fda3c10b70124aee1268b896486b4a05cc47 docs/models/multiattachto.md: id: 056e50c9e976 last_write_checksum: sha1:654078650712620389db0a568669db36597a309e pristine_git_object: 9a835b9ec832a90edf8b764f3deb58c76b310992 + docs/models/multiattachusagealert.md: + id: 645322c11b62 + last_write_checksum: sha1:8ce33b7ce6b3d2e5fc1b0757e46e75c898251258 + pristine_git_object: ef4d50d8835ef14da1eb8917e7ab8988c5e3e44d docs/models/opencustomerportalglobals.md: id: 9672e1095d8f last_write_checksum: sha1:128710c47318a46258c878691506ae599d1b55e3 @@ -1589,20 +1657,20 @@ trackedFiles: pristine_git_object: daea8b231352c8ae8fc3d23ab739ed3ce60df632 docs/models/planbillingmethod.md: id: ac90cee4f6c5 - last_write_checksum: sha1:1a7f86d46499a4f95ed7de36d79c3ff227baf98f - pristine_git_object: 8e4f8a8d96a6a110275c7b80ab0b93dfc9cd3ebf + last_write_checksum: sha1:30a815d6adf61aea6127d9edb4026936d13a4250 + pristine_git_object: 4aa20f7a70f16a7f437aa35b04841d8d4b19780a docs/models/plancreditschema.md: id: 510cd9d4f287 last_write_checksum: sha1:3929c72df37848348916c755926dd1de7c252b3e pristine_git_object: bcf7cd000ef324a7eccf32e58c7195627bf19a29 docs/models/plandurationtype.md: id: 7eb9a5f1eacb - last_write_checksum: sha1:7f711eb7f7240261214a728b8f62522b9463a7ed - pristine_git_object: c08021fe0315ee4efd0dd8fce71de18150612858 + last_write_checksum: sha1:81f41818daf55dd9dadc8e14e383ad2e62804905 + pristine_git_object: 251f2b661e3a9d65612ac18f0779bc04f515619d docs/models/planenv.md: id: 0e584adaa5a5 - last_write_checksum: sha1:eb12873dc0baecf10b0aa8d8e8dd4a58bb7a30bf - pristine_git_object: 7a5339582592990338e74eb735a180e4c4ecd7df + last_write_checksum: sha1:1afd6a72ed0428e605f36df7a4c8f7f24c728686 + pristine_git_object: 2357a78eff239943e206394ab2884612b88fd5a0 docs/models/planfeature.md: id: 81977f66cb0b last_write_checksum: sha1:22bc5373900e56f572d8e2e564bc9bbb6e5967f2 @@ -1629,36 +1697,36 @@ trackedFiles: pristine_git_object: b28dbe1c0b38e63dcabf1d16b2265b5a97d2665e docs/models/planpriceinterval.md: id: df92467f108c - last_write_checksum: sha1:4619925dd8e98bcf7b8b6d727dde8a4b95d252ff - pristine_git_object: 490e7c4dfc76d81dd6b7284c76d689d1b926bc48 + last_write_checksum: sha1:6da158925940c2762a89484fc02a197a45b84846 + pristine_git_object: 3c6bfe6cc9f917cfb28a90085e265b3b5a103584 docs/models/planpriceiteminterval.md: id: b41d93ad5140 - last_write_checksum: sha1:a6db3df878cf0834ba27d9017313d2a708010e55 - pristine_git_object: 7543c11b27caf7c212e0a837a41d80fd9801bbf2 + last_write_checksum: sha1:33f6566c42e361d3398e74011ea043ba92e27e61 + pristine_git_object: 6cab31656b0f63c55b9aee19b6f8aa3eef2b59f8 docs/models/planreset.md: id: 08cb2753f6e6 last_write_checksum: sha1:aa46714be96b570fdd130828a57235036711aac1 pristine_git_object: 03a0310de42bc4e702adbc197316ee0df9f31322 docs/models/planresetinterval.md: id: 1d0c7c8f25db - last_write_checksum: sha1:6e1c62bcb913f380ca6f967bdb40714bc99c6393 - pristine_git_object: cfd3fd0d99536ac7153a5740ccbea7248ebb7eae + last_write_checksum: sha1:e37a9e1141c5c4c9a398d5727e967cc7eab65203 + pristine_git_object: 0612c99bc608b137640e93683b223efe0a2ffed7 docs/models/planrollover.md: id: d432fcd32015 last_write_checksum: sha1:6a9d0e59d5e260cacebab72874b6f9b52cad8269 pristine_git_object: b24a347af2dd3725b201c0228c293c07296ad436 docs/models/planstatus.md: id: 1b32a1788a18 - last_write_checksum: sha1:74508405c05c6e93bbe1583facd1895c813f18f0 - pristine_git_object: 65802bc0e9baadd827bc460a5ad655acc1298888 + last_write_checksum: sha1:f1d88fb70f2b1828a393d8d48d061a46a4c40070 + pristine_git_object: 23f15ebf3bb4574dc2228b8b042095d477f3614c docs/models/plantierbehavior.md: id: eef000779cfa - last_write_checksum: sha1:53e33ecac045604feabfab5d02a5caebf791f03e - pristine_git_object: 9ae9f32d44709187aa8d6805f92489ab7934b60c + last_write_checksum: sha1:c0c1ce53d480992c255681103b9a3f8233b189ff + pristine_git_object: 563c3a51a6c8f347a65899fefcfca11d43139da7 docs/models/plantype.md: id: 4c86df78e60a - last_write_checksum: sha1:5246ebf75c009119cbfcd501cdeb659e9f3c6786 - pristine_git_object: 5add1a3cab3d01077452a4df8d9037c6279da319 + last_write_checksum: sha1:f78cd88fb5996e96029ce1c66c8b76f80a83a05e + pristine_git_object: cf233509e8cc1599a589dba1e68dda9e2d16400b docs/models/preview.md: id: ca71b601ef12 last_write_checksum: sha1:40aa929c88e329add6e7ca46d7a8a2035a292320 @@ -1673,8 +1741,8 @@ trackedFiles: pristine_git_object: 0161cbf90c9d6601844f3c10578fbf2ce2a7e8fd docs/models/previewattachbillingmethod.md: id: 4fbccec2190f - last_write_checksum: sha1:8b0d83a3e51808880340322fe4e928c15db8db80 - pristine_git_object: 51af469aa8274799746f5b5840dc83d9c6bc22cf + last_write_checksum: sha1:39e5c7556d0f2739d994e97698da4a0dba485cbf + pristine_git_object: 1646686d9a3177cf398ce904346a810566c681f7 docs/models/previewattachcarryoverbalances.md: id: 2f80251494f6 last_write_checksum: sha1:12f176856ea0429bd9a025357aaad945434e9c09 @@ -1697,12 +1765,12 @@ trackedFiles: pristine_git_object: 9af81284fbe5d16a90a7c2c6691207c556f1bd74 docs/models/previewattachdurationtype.md: id: b41ef967b1da - last_write_checksum: sha1:05fa10b05bcb98343d3e6a5c4af64714fa00e7e0 - pristine_git_object: dc12d9b4e678870280da54412df0ab657c29b7bb + last_write_checksum: sha1:fc6a07373e3dfeaa2d6232479146ffb8b1ab0881 + pristine_git_object: e9584b37ee5121b526d9dde85fef8232ee5f30e0 docs/models/previewattachexpirydurationtype.md: id: 0cb588a15167 - last_write_checksum: sha1:8044289f94f1aaee29febf2a20de8b6ca326eb59 - pristine_git_object: 2034fa2473708010cd8f25fe4fd21c4ec6351560 + last_write_checksum: sha1:e87687f7de9a6c1e425ed3adee650ce2d3ebc445 + pristine_git_object: 1ab76bb0e863b774266c81ea67f8fe98842deec5 docs/models/previewattachfeaturequantityrequest.md: id: c17c4de9916c last_write_checksum: sha1:877dfd0382ad30f95c6a47a3024cb7211b3449f0 @@ -1717,8 +1785,8 @@ trackedFiles: pristine_git_object: cb44702b2c3aa86727942ef1627f50a508365304 docs/models/previewattachincoming.md: id: fe006ad43864 - last_write_checksum: sha1:52844170ce012cf824496aa538e7f54dacdc3ae0 - pristine_git_object: 395dcbf05135f557846dc5732276c6441a5b0aa5 + last_write_checksum: sha1:7a863bc4b613f84a6ecad4b358d40f72af260db2 + pristine_git_object: 5caea4f0bf69332b63192d152cb733e8bca9d7ef docs/models/previewattachincomingfeaturequantity.md: id: 6cac4cb0ca48 last_write_checksum: sha1:697fd3000cdf5febf7a78846223b52fa115714aa @@ -1729,8 +1797,8 @@ trackedFiles: pristine_git_object: 52153f9fd9a587fd6e60b8ee15bbd9e661d7bb3c docs/models/previewattachitempriceinterval.md: id: 96eeb2602066 - last_write_checksum: sha1:fbe0200ede8f1c6acedfc320b2422a73c2b966b6 - pristine_git_object: 4d34aab005e0e16a8f9a250e0a76011bc679afe4 + last_write_checksum: sha1:dc3892f6e3cf2aca0e20c16f12972049b1dd9c5c + pristine_git_object: 7ebceccacfcc32aa5ad5eb937660ef3739ec7f82 docs/models/previewattachlineitem.md: id: 3411ba3438a0 last_write_checksum: sha1:7c2df8dabcaa3fc464ccda62846c7dbc36820be7 @@ -1757,16 +1825,16 @@ trackedFiles: pristine_git_object: b368084e508a42672d02974a249e4bed426a1bbb docs/models/previewattachondecrease.md: id: c3d14ec84b6b - last_write_checksum: sha1:6cc38137f4d3f59dfca3c40ed77938a82977c64f - pristine_git_object: 803832bf36aa416616d9e0bbb740b7c63b7087ae + last_write_checksum: sha1:c98eabf793906b34cf479e252ebb67d61a590999 + pristine_git_object: 6d8552af77f228f5f06a56035fee0e711310d94a docs/models/previewattachonincrease.md: id: 62e686313af6 - last_write_checksum: sha1:feb4f85815fb574e36cb16dd08d3046f14dea8b2 - pristine_git_object: 28ea912a0cbff919f7f93dcdd9b920d4e98b40f2 + last_write_checksum: sha1:79c9eeae5214b2e2d4800667d6b188537c7f46ee + pristine_git_object: 2fe65b7ad59a16fa648125ced2c2926f5ecbb8b9 docs/models/previewattachoutgoing.md: id: f07be0abfe91 - last_write_checksum: sha1:0127c67d98d394894962dc58bd0e421fbfba8bad - pristine_git_object: 3fa35595df2a36da3c72d98fc55533fee2669c7e + last_write_checksum: sha1:ab1374a15bcf61fb5bfc845e18e08a158b32536b + pristine_git_object: bfe2bf127938eee77b8111757ca59d5494a5d47f docs/models/previewattachoutgoingfeaturequantity.md: id: 962820e0d447 last_write_checksum: sha1:24ed539aa370712307b51b0b1514d95314e9bf3c @@ -1781,36 +1849,36 @@ trackedFiles: pristine_git_object: 582c2b96374f7dfba538a73d21776a7b1cb0eebd docs/models/previewattachplanschedule.md: id: 487cbb8bd9dc - last_write_checksum: sha1:6b50ca928452851bd01e971838cceab1eb76b8ba - pristine_git_object: 984afaf416460db09828280b11c62a08b2bf808a + last_write_checksum: sha1:d3b1ed2650f3c52cc1d9cd2e8b27b9d578e37904 + pristine_git_object: 7551150c582828b16198182257edd1605998c2b7 docs/models/previewattachprice.md: id: caa899534556 last_write_checksum: sha1:5c7bd82d2e4fe15f72769b958b47fa3ae78363d3 pristine_git_object: e3251f374a50de887dd74b891d22d21deba9ee5a docs/models/previewattachpriceinterval.md: id: 922bda88e1d8 - last_write_checksum: sha1:e1e1f147773cfc8dd61892c630dd97cfb7f689a7 - pristine_git_object: b2d88314d2a82c87c5b8d8d26268831ba98bf8cd + last_write_checksum: sha1:8e38cdd00dd58a77eb13cb133c4f5b31d865659e + pristine_git_object: 052b4f55f00c37535e83043f2ff325f09ffba1c0 docs/models/previewattachproration.md: id: 2d9f5cd09535 last_write_checksum: sha1:c92886c2a07120bd7c0750baa33f62352a8ef3cd pristine_git_object: 86fef8897a946a21a11d01bb7ef680ef4d5a689f docs/models/previewattachprorationbehavior.md: id: 657d60d9fd5f - last_write_checksum: sha1:921ed0bc580bea1048a91c8c5ca3427c4a5005fe - pristine_git_object: 1cc089658a20a07a4f3d3a449f7373cf36037a74 + last_write_checksum: sha1:564a23e8f0cf2f3f64e729bcff18a1325986d828 + pristine_git_object: 03ddbe49817478936f3c3998f6aadeb8a85cc8e8 docs/models/previewattachredirectmode.md: id: 91c46d6fca53 - last_write_checksum: sha1:e2cfe9849731a8211e6a3ef3363f697bef6d41b4 - pristine_git_object: 48572ba5199560cb72cd3b61cd0ba32522224985 + last_write_checksum: sha1:fb3c579d52cdcbd9a99e0c90f2b92a12d005c816 + pristine_git_object: 6c7e843b528997fe4f5d10a83aedabc5fb08b2ad docs/models/previewattachreset.md: id: 8a677b69d681 last_write_checksum: sha1:441c2f04c2ca1f41a7e0251a2cf448fa63b4df92 pristine_git_object: 9f0dbd39952d86ed4561fc5cf6f573cca79b9221 docs/models/previewattachresetinterval.md: id: 62df338dbcd1 - last_write_checksum: sha1:07d10d908f65ed24de6d6962a736d0bbe7b2b7e8 - pristine_git_object: f93e84304174b521a52bca93cb58a1adef492b4f + last_write_checksum: sha1:3a48a841957e79e2dca7307a4b7222f84bad1732 + pristine_git_object: 3e9b1be359f8832c3439cad7e04566bb40ceaed1 docs/models/previewattachresponse.md: id: a678e8dbf94c last_write_checksum: sha1:e9e75a12ec734c69bd71489f74ee3ffe501c5a04 @@ -1825,8 +1893,8 @@ trackedFiles: pristine_git_object: b19c21c8f9a0b093a22fa6125ab43cc78303ba81 docs/models/previewattachtierbehavior.md: id: 115ad37af215 - last_write_checksum: sha1:d973e49f3abd3214eac6c3d9e300afaed7112bdd - pristine_git_object: bffcbde309d5b3835199bd4a9e5781ee6f12c199 + last_write_checksum: sha1:718d895201d280573f54d2c4e1d0d913bec08399 + pristine_git_object: 997335a91b67a1088b21bd08dfd8086d5ac77d04 docs/models/previewattachto.md: id: 6eea2cad63db last_write_checksum: sha1:f942419300ba675c7fe00f0675f5af3dab8e0eba @@ -1849,12 +1917,12 @@ trackedFiles: pristine_git_object: 2562b1f2a43232ca2e683c75865c8c3dfd160e50 docs/models/previewmultiattachbillingcontrols.md: id: c19e8fa54bcb - last_write_checksum: sha1:560b387986c09d14d6467c6d74bda8737c2d2ea3 - pristine_git_object: 2724646baf3af08b856069e00fc2030d0e7c8799 + last_write_checksum: sha1:b2bbbafb91f36d0f6c5bf5037f65ccf308303d88 + pristine_git_object: 4e738a0c4a0c320c8069dbe54ed9c8985eb20a55 docs/models/previewmultiattachbillingmethod.md: id: c92637a0950c - last_write_checksum: sha1:5a2f38a6ff4b85777d401dec9b91b6d45eadfa30 - pristine_git_object: 91adfcd4a6f59f413eebeff295892db454459488 + last_write_checksum: sha1:b3b600448b1f704d40dab1df259d606c3e44ab5e + pristine_git_object: 8bb4488808e3af817c5331cebfd0fbc66fbc4323 docs/models/previewmultiattachcustomize.md: id: e2e0b7d3e939 last_write_checksum: sha1:31e8437aa83882d1f206b0203cdf25a7b37932f1 @@ -1865,16 +1933,16 @@ trackedFiles: pristine_git_object: b1c9a90323f5e33a46456439d20629a7906f68b9 docs/models/previewmultiattachdurationtype.md: id: 6cc4db1fee39 - last_write_checksum: sha1:21875f45ce0041e9fa694918b0394aa91a439980 - pristine_git_object: 1ca0708fa51d5438d6002860427998538f3cbc07 + last_write_checksum: sha1:f54782456671576c1838d7ce7da24792d364d867 + pristine_git_object: bb524f83d18fc034a1d880967f6527e5be51ab41 docs/models/previewmultiattachentitydata.md: id: 1ff14291b34a last_write_checksum: sha1:ae0bb74d18a762c6e56562c4ac846b28d8ea5b4e pristine_git_object: 1089f8e85315370f7048685132b775c5bfee526e docs/models/previewmultiattachexpirydurationtype.md: id: 44455bd096b8 - last_write_checksum: sha1:8b5ac7e334e3d6647bb4baaa4e758a5f11de33e0 - pristine_git_object: b99eb21cec335eba7578deeee4e7b1ec740821d3 + last_write_checksum: sha1:02af32c341d9fcfe16d35330de4e3088afe095b6 + pristine_git_object: 6be2390f331c2386a20edaa9bfb1eb18c93e998d docs/models/previewmultiattachfreetrialparams.md: id: 784eef5ce253 last_write_checksum: sha1:3965d49ac21ea4c59455be2aed5708d38a316e8f @@ -1885,8 +1953,8 @@ trackedFiles: pristine_git_object: 74932d096dad7119db816c3ce9b93c75a64dcf0b docs/models/previewmultiattachincoming.md: id: aac62231accc - last_write_checksum: sha1:ca52a1e1cb473cb50f3496f776a73b0d9c427553 - pristine_git_object: 10e5bba08891c6294a7328644a969dc730736ec8 + last_write_checksum: sha1:d4959b18c4aead16c5b59d85ad717d818bb3321b + pristine_git_object: 46ea37fa5e57ea99fa0efc3121747186a791841d docs/models/previewmultiattachincomingfeaturequantity.md: id: 0d1c8af25b06 last_write_checksum: sha1:ba7e32dc427387e3bdcb87f7b417c23bec9fa484 @@ -1897,8 +1965,8 @@ trackedFiles: pristine_git_object: 516159a7040694c7888368e96a319b35ebd29887 docs/models/previewmultiattachitempriceinterval.md: id: 8ca44d192cbe - last_write_checksum: sha1:92e48e53a5b39b07b98d248b8bf7cda63053c64b - pristine_git_object: 0d85a18353ad4537cda3cfd68537f0b8cd96a1a3 + last_write_checksum: sha1:a0d06f55cc6f7cbbe7be3c0154ecd36aa8281e2e + pristine_git_object: bc25b0798e2a53349a757957a31bc42b7ffca927 docs/models/previewmultiattachlineitem.md: id: 372c4f319256 last_write_checksum: sha1:16a0c4e95728c97567e80479d6fec964b275c6c4 @@ -1925,16 +1993,16 @@ trackedFiles: pristine_git_object: 7c466982c51bf23d41533d2bc7117aa3471cb679 docs/models/previewmultiattachondecrease.md: id: 21532d46903d - last_write_checksum: sha1:69cd2530ebc7ef3b0875a6167e5e3a3ad60a6be6 - pristine_git_object: 0db73ef90cb74f48674006c66d71f705f9a8ffa9 + last_write_checksum: sha1:4b591c017da9bcf618d7334146a826ec658da677 + pristine_git_object: afd67f7d33a2b32a6722dc13aaea972a1657693f docs/models/previewmultiattachonincrease.md: id: 49df4f246e99 - last_write_checksum: sha1:7aa7e4615fe336d4f4b8a257e9cdb2196707cebb - pristine_git_object: d002ec146cb44a309fe32ffa4480975ea764d936 + last_write_checksum: sha1:da305ad824c752a1c618eb8053ae955d3890bd28 + pristine_git_object: b13c6cba97d2aea223ed63097abbd628467c1b97 docs/models/previewmultiattachoutgoing.md: id: 8edd3579b543 - last_write_checksum: sha1:5a259c89a52e2e0510a4f43fa017421f6350ad15 - pristine_git_object: 673233ff66246c788df1abd9909acf38d9207652 + last_write_checksum: sha1:8d17a634d48de6a12be7887c0584b16c901e243f + pristine_git_object: 8181a24468879f2b4d103976c5cad2dac41d4256 docs/models/previewmultiattachoutgoingfeaturequantity.md: id: 7469536a99c4 last_write_checksum: sha1:fe2d8764755bdbea017ce1e595634b328dbd2243 @@ -1961,24 +2029,24 @@ trackedFiles: pristine_git_object: 3e020fd0292ca357afe2fc1639370799b060c530 docs/models/previewmultiattachpriceinterval.md: id: 2b2601649b6d - last_write_checksum: sha1:c2806dec52c16c53d5b9073cf810a42b30a854a8 - pristine_git_object: 40a72a65b17f2bc402302eb86fbfa202accccea9 + last_write_checksum: sha1:bf76a503f0cfbbfe4f0ca3baf62307ce38ee766a + pristine_git_object: 84dc91145b0cf810dc6f3508d439746eda6a3f55 docs/models/previewmultiattachproration.md: id: a3b6b1d2d9fb last_write_checksum: sha1:affa177d9b1b98eeadb94b3acbcc519dc46c4c01 pristine_git_object: 0a96249ff6158b25f29c66e7fd321fc0ed9a3251 docs/models/previewmultiattachredirectmode.md: id: 3190f18130dc - last_write_checksum: sha1:d6850eaf2f0434dbb0f29505ceb501e2c1ee2f92 - pristine_git_object: e5b39411e78efc936b169a8127b02bd221695852 + last_write_checksum: sha1:a342d6a1157e69b06e071933038bc018295ec72c + pristine_git_object: 57584818b587fe4012cc335c2271bf30d2d7baaa docs/models/previewmultiattachreset.md: id: 38ec56a354c4 last_write_checksum: sha1:6227feb0bf39f70a8bef3be1ce0ee31017916034 pristine_git_object: df7d0ec6a0095ea3e6d1d2989eb2fa826f920229 docs/models/previewmultiattachresetinterval.md: id: d0d871495a37 - last_write_checksum: sha1:3cdd8557b3415144f001b6dd8e107de1db3f6518 - pristine_git_object: 1848a0347c3b21aede196a3e3f4edeec7a25fc3f + last_write_checksum: sha1:ab91f083983445f20326eb03068a6209bed862d5 + pristine_git_object: ec56bdecfc8eac78fffde4302cee7caed963686a docs/models/previewmultiattachresponse.md: id: 122c343c9098 last_write_checksum: sha1:24a707473c614f9dc2140437bf133f0ca909d155 @@ -1991,18 +2059,26 @@ trackedFiles: id: b39c2ab7095d last_write_checksum: sha1:b80d2c15524fdafff223d739c771aebf698a907a pristine_git_object: b64826019f5623f8c027aec5e48276d47bfcf156 + docs/models/previewmultiattachthresholdtype.md: + id: e4eeccd64018 + last_write_checksum: sha1:4bb9f18b372ee5f1841f41362c356bf11d902bd6 + pristine_git_object: a0d6fcfa11a9c05c430f3100bffc81e956a79d5c docs/models/previewmultiattachtier.md: id: a075b8d3b1f1 last_write_checksum: sha1:912c2757ffd96b98d56d3b8f9aac64bde23fe83a pristine_git_object: 5645fbe7df82ded057a21286f80c5c1c84b80f28 docs/models/previewmultiattachtierbehavior.md: id: 698c1ad42dcd - last_write_checksum: sha1:fd279290c7cca361a118b2e28bb90cac20a47cc7 - pristine_git_object: 3a1e36ee96679f5c25318345e82f8b0a989f3046 + last_write_checksum: sha1:3d8257623eb02e7febc36d389be1d536e3137939 + pristine_git_object: 76548a276b8599730a680f9aba2add68b23b82af docs/models/previewmultiattachto.md: id: f34b59fd5a7b last_write_checksum: sha1:bdad4bc0149814f1518a28835e98401bd06d392d pristine_git_object: 00224614a0f4c3cdbedca57e04bbb8b36b11ce2b + docs/models/previewmultiattachusagealert.md: + id: 5240ad54b06d + last_write_checksum: sha1:3111977f86d50bb7c32041f334f39b6e386fc5fc + pristine_git_object: e2444fdf567e1b043c746965f0b90c61710b27e6 docs/models/previewmultiattachusagelineitem.md: id: fee1f87cb24e last_write_checksum: sha1:f10784c42022a6168338580e5a1b1d6f3f0b1aec @@ -2017,12 +2093,12 @@ trackedFiles: pristine_git_object: 201447b16faaf38aecc9d2deb0044a28b17899a9 docs/models/previewupdatebillingmethod.md: id: 1d817ab2aabc - last_write_checksum: sha1:a0b7a400f38299b5ce557377ff1a95bdc24864b4 - pristine_git_object: 73489cd257f46774a76c73aea4988873bb7c64da + last_write_checksum: sha1:d7eb39bc6e0796792c173162dbcc17b37bdec535 + pristine_git_object: c8f89cb2d5e4d8003fd01c88a0d27bb11a766a7f docs/models/previewupdatecancelaction.md: id: 1159f8f85008 - last_write_checksum: sha1:95466e097d9f94abf963baac67bc04ba1556ad70 - pristine_git_object: 41e8d142a8eb87255d707fd3a8d6adde82058735 + last_write_checksum: sha1:ad6cfa08615dffa789adb8e42f63ca30ea353582 + pristine_git_object: e5347d9975f47094bea63fad5e615812c2d8373e docs/models/previewupdatecustomize.md: id: f4f7d5f4d0a3 last_write_checksum: sha1:9b6be59710ddb51f409f42d09e58ebb3f81b49dc @@ -2033,12 +2109,12 @@ trackedFiles: pristine_git_object: 29c1b1e47f5f57e40598151b8e7413694014fc1a docs/models/previewupdatedurationtype.md: id: "791319033460" - last_write_checksum: sha1:02779e1548b69230ce75425e28f9b0b30f3a1816 - pristine_git_object: c63a6b7f1c0fa279cc6c523ff867a47a2ba3f4e6 + last_write_checksum: sha1:49c51941f13f003c3fdb5dc94030bdc9202217f7 + pristine_git_object: 8a52c0c6cd095240f50bfad29a802c3485c59504 docs/models/previewupdateexpirydurationtype.md: id: 6d5273579d32 - last_write_checksum: sha1:0d69e9b1163c93d4c4557329004ec27797ce40d6 - pristine_git_object: dfa228c4dc2e4a3ce94bde61fab7db93629e927d + last_write_checksum: sha1:18c52730d9304d9ae75a1f8e8506a687b4962690 + pristine_git_object: dd5866b2538407dbbe1274f8a38edaf9597f85ff docs/models/previewupdatefeaturequantityrequest.md: id: 8f682788e1d9 last_write_checksum: sha1:819aa474d07da86a55bfa1e42dbc66881bf42925 @@ -2053,8 +2129,8 @@ trackedFiles: pristine_git_object: a2643aff71680053edeb399aecdfd771e4ca4e07 docs/models/previewupdateincoming.md: id: d62db8877e6e - last_write_checksum: sha1:0d08d961875bbd9d756422f3851add546c2906ab - pristine_git_object: f97549d9733aedd7da98163b9417863d9bad2a90 + last_write_checksum: sha1:b2a24574247aaf1335909a4e29db2042c6811a92 + pristine_git_object: 7b4c5d968a5b4e84af9d7b8a381b33e0fd235ea5 docs/models/previewupdateincomingfeaturequantity.md: id: 76ae479acaa8 last_write_checksum: sha1:ac92bcc9ccc296703d0c839fd5c68bc59c00bd12 @@ -2065,8 +2141,8 @@ trackedFiles: pristine_git_object: 7210d48c7d789b0e78753c7280072daccf611894 docs/models/previewupdateitempriceinterval.md: id: 5317a2333cd5 - last_write_checksum: sha1:9439782c8faf5679e6dc27f749dae7d73f1fb9d7 - pristine_git_object: 7542efb89bf43f3561ffa9ceba43149394ba2b2c + last_write_checksum: sha1:783ba76f51c898f9fcfb9fc9cc9c0c681236fc56 + pristine_git_object: 4d03b6784e60f4848254431479f0c6af337aad0c docs/models/previewupdatelineitem.md: id: 74e43e80addb last_write_checksum: sha1:e5b6dfc60e56592fa18122d3407a3885d2eeb40a @@ -2093,24 +2169,24 @@ trackedFiles: pristine_git_object: 833e7fa1bfa9421f35c24e27e791ae66c5a76b21 docs/models/previewupdateondecrease.md: id: a9a3f1b4aa0b - last_write_checksum: sha1:0ef2771526ada399e393e0909fff9f780508d52c - pristine_git_object: d611229e7808124b73329fda44414ac630e755a3 + last_write_checksum: sha1:2aad8132e9e21f2ca8260e04df4811d8c404cb69 + pristine_git_object: 1ca348c8d2f4a676d111a18d74ac9d216cc84500 docs/models/previewupdateonincrease.md: id: 391e207385c1 - last_write_checksum: sha1:426b9792fc34751e6afdab45413d40075b4f92b5 - pristine_git_object: b4267c511b2c2438d04aaa25f05391bf9bcf4352 + last_write_checksum: sha1:f8d0c7936a1260b5d0dff01f8ce55e2407ed1c24 + pristine_git_object: a36f192fcdcb7733199531049b545b65609b7d50 docs/models/previewupdateoutgoing.md: id: e6bb92550ddf - last_write_checksum: sha1:d37af5e3339abb59af969a3b02c80326ffaec6cd - pristine_git_object: 370bb5c920ead8598b45098532738c832250311d + last_write_checksum: sha1:30c9e334b9ef67d769a66b1a7e771eb3d6b5e7ad + pristine_git_object: af492b12f6e967a5766a7ed15bb8fa41a86183bf docs/models/previewupdateoutgoingfeaturequantity.md: id: a48228c46fc8 last_write_checksum: sha1:12473723f7449656c018793c8dd4d60fd1b76d68 pristine_git_object: 539712ac6959711f463c590e0a1e981b1a01cbff docs/models/previewupdateparams.md: id: cf952c2251de - last_write_checksum: sha1:e55f62f04e137489f3c6e0d19d02dfffb587e353 - pristine_git_object: 28bfd625db69fb28d1bf6046f168d21c9b1b54f0 + last_write_checksum: sha1:a6c48474ad4eb209d986dd3432e7f61e9d6a12c7 + pristine_git_object: 5185840e5bd0c276789f463ea4016a79c07fc327 docs/models/previewupdateplanitem.md: id: 741880702ddb last_write_checksum: sha1:a5256a07545cdce6e0e068e491f3e5ed791201c0 @@ -2121,28 +2197,32 @@ trackedFiles: pristine_git_object: 636b73a20517aebee5661b5bee76b578c8adb2e0 docs/models/previewupdatepriceinterval.md: id: 300f908b2503 - last_write_checksum: sha1:c571ca192d0bb1b7b26cf6f450c1051aca1d9935 - pristine_git_object: e452757bb557a2095a402cb5b6e0b4127e194409 + last_write_checksum: sha1:9c8866732d1777ff53a167166429c484d451c997 + pristine_git_object: 14a103c8e7e3ee88d61dbd99759a9f803d14ef8b docs/models/previewupdateproration.md: id: c3e1d3131ff9 last_write_checksum: sha1:f75548bc7c94e834b8ec319bb98784f4abffc6d5 pristine_git_object: 2dcc8bc0df391567537a05ef4f50abf24a365483 docs/models/previewupdateprorationbehavior.md: id: 7989e21d8761 - last_write_checksum: sha1:472410fa4891707445cc134a4a2cab1028146a0e - pristine_git_object: f09eb4baade271cd90673805ea85230e5fbc9c49 + last_write_checksum: sha1:c93d8760bbfbe3a0f3b2bc2a1efa5424ef9373a6 + pristine_git_object: 3bad44b745b15ea8e77d39c9220596c650c079d5 + docs/models/previewupdaterecalculatebalances.md: + id: 33030c0b1996 + last_write_checksum: sha1:d8a148d9bf0ae35f8013c410e25119d8151b926b + pristine_git_object: 3f7e04f7509a9d64ead05c2ae326627cb8ccb088 docs/models/previewupdateredirectmode.md: id: e216ddc3986b - last_write_checksum: sha1:130d0647417b2cdd3b463aed2782977ee2d48a39 - pristine_git_object: b7fd0581ce5e2fc75f78a16f7068aa9a1a352580 + last_write_checksum: sha1:ecac0b945f80a6f1b79349c4e3e2963b8c2fa0e3 + pristine_git_object: 96d050edfd6010ce162f352cef87d7c75687f05b docs/models/previewupdatereset.md: id: 0575a09e5ac1 last_write_checksum: sha1:20aca504ecf7ce4d56eab0ebf69af981c7f2903b pristine_git_object: c85d5a3300fdaa77bb45b4d92321c92ddb1896f6 docs/models/previewupdateresetinterval.md: id: f23e968cc58e - last_write_checksum: sha1:e42956af55e53f45c47c152873d8235e29b7e48b - pristine_git_object: df69f8a61b19667d538abe303032542b39d111f5 + last_write_checksum: sha1:c4f1239d2233e77448908ff5eb78b17bbdd9ff94 + pristine_git_object: 8290e0f1adeceace60c5189e9d8a2cb0b11471fc docs/models/previewupdateresponse.md: id: 4a657c603c51 last_write_checksum: sha1:852ed327e9ff9a651f734d797a66089dca3f078f @@ -2157,8 +2237,8 @@ trackedFiles: pristine_git_object: 0c481f87403fa61b8209429b58cd9a89d70416e6 docs/models/previewupdatetierbehavior.md: id: e0ecb90f6fe5 - last_write_checksum: sha1:aed1b00821f049d0d02433e8e27d942ecf3d5888 - pristine_git_object: 9b656bbc1ac9af47ea43144e53f195f7510c8499 + last_write_checksum: sha1:a53cb1a4ea349f2d8f7b0c657c16a0035d379f59 + pristine_git_object: 8a5b3f74ff2494837ea1d6f2085569c78a960472 docs/models/previewupdateto.md: id: c8706fb2f15e last_write_checksum: sha1:ee0c86ede67f157bc7d60c0dbe8c11567ee98418 @@ -2181,20 +2261,20 @@ trackedFiles: pristine_git_object: 22e14f97a475eec1b273ba6e10063f0e777aa6b2 docs/models/productscenario.md: id: ab3587084a21 - last_write_checksum: sha1:c0d596fc265da91e77bd5be7962bf8738c6ffd6a - pristine_git_object: ef93f479e1993ff12a83734661ed5640d6d074c3 + last_write_checksum: sha1:34b69aa0f068905c5c668fef314d6d278865864e + pristine_git_object: 3c82690287597c8ab32ec97a8f6f4150aa78f58a docs/models/producttype.md: id: 2c019befb41d - last_write_checksum: sha1:978c5475fae093e6622a67c300c4caa3ca4f5323 - pristine_git_object: 9bdbe295958794c930d6056b4ecc8f1c448323cf + last_write_checksum: sha1:3dfda58f5087dfee73766c1a24dbf64bcd18a6e7 + pristine_git_object: ad0e92bda3041d17df41475adeca765b6cba81f2 docs/models/purchase.md: id: f872769b6939 last_write_checksum: sha1:b250603c69b08d953b6f2dde179eec8606d27ef9 pristine_git_object: 133bec63c1dce6f39d3954909e188dae71f3e2af docs/models/range.md: id: 0cae0c76762e - last_write_checksum: sha1:ae76473539105f50e728c1657bf5d2af3699da0d - pristine_git_object: 607ea6d29d56b0fa97a1a99a06d2b3caccda78f5 + last_write_checksum: sha1:617c58ce88db53abb0e38d60cf2027034fca69dc + pristine_git_object: 88e64b85535ba90767889f32db386fadd2d0b94b docs/models/redeemreferralcodeglobals.md: id: 5640df85921d last_write_checksum: sha1:44f830b7a0730a261b0403ebb6a949d9ef7f1918 @@ -2221,16 +2301,16 @@ trackedFiles: pristine_git_object: fcbd3790d73d6a7157b9665d8221be09af8822bf docs/models/rewardstype.md: id: 338992fe95d5 - last_write_checksum: sha1:224b80dbd92ef68bd770b3a80bf937ac43733556 - pristine_git_object: 8b34bbb0cd357bcc79eb45b0e7f4ce9b6399f846 + last_write_checksum: sha1:7eb06abf43f702403e324aeab931a3affd4a37ab + pristine_git_object: e3e5b0cff2939b70dd449114ac63bd2787ff8732 docs/models/rolloverduration.md: id: 6338776421bf - last_write_checksum: sha1:0fa5a19f470504e5ef7e45057d92f6481c2b7d3b - pristine_git_object: 1415e465a1f2966167dab17d9fdafde292816ba2 + last_write_checksum: sha1:74a9d6e9ee3291773a9010467a0d26721203f6a1 + pristine_git_object: 556f622fc78575df4efda34f58c9dc8c7da7318b docs/models/scenario.md: id: e3aad8ab5efa - last_write_checksum: sha1:6ce9a6e38f0a02622ae273bef4c8a6daa91794fc - pristine_git_object: b1f3a49d0d84ad314bb2103a706454e1314edc06 + last_write_checksum: sha1:132dfc4d6a03ff1073be4390633a64afd909c700 + pristine_git_object: 58c924cbae30e7c3a227a04ab9655c97381be99a docs/models/security.md: id: 452e4d4eb67a last_write_checksum: sha1:64787360e0bddbe1d2d2ede91992fa1a27c15a0e @@ -2245,8 +2325,8 @@ trackedFiles: pristine_git_object: 77867b1b1a7917f32c17387092ae69eed9f843d7 docs/models/setuppaymentbillingmethod.md: id: 3048b32fa76c - last_write_checksum: sha1:e848284bc3792e584e4478b9e56f338917c181d9 - pristine_git_object: 82e28aa321e92c94ae8b30336fce42dc3dd07042 + last_write_checksum: sha1:128a736c42e76a6af1eb0350ad3b99068867c400 + pristine_git_object: 50b7f326fbf3fc4125a453dd50422f5dcb80c296 docs/models/setuppaymentcarryoverbalances.md: id: b4b6d5e8b123 last_write_checksum: sha1:9b90774e7eea5c65992df6162ea394e7e81c714f @@ -2265,12 +2345,12 @@ trackedFiles: pristine_git_object: ef754a56df7fde8ddfe8a0f78afbe70876c82ccd docs/models/setuppaymentdurationtype.md: id: 05682f9ba3c6 - last_write_checksum: sha1:2b08dbc92d5b1619dc16528ddf535c0a068acb0c - pristine_git_object: 1e3214d3f28ec62115f8da3d243d536c6d27e4f2 + last_write_checksum: sha1:727d37a22da29f321f3b1104bf36a7ef09ca3d89 + pristine_git_object: 2f50fe56b53431668fcd8ffd939345127fee8f04 docs/models/setuppaymentexpirydurationtype.md: id: 31b912769b88 - last_write_checksum: sha1:39302c6bac58e2b7a336b1425b95851948188b66 - pristine_git_object: a0e7afc3066491ef449dac6931035965b66dd99f + last_write_checksum: sha1:59a3ed79a04580b80a36c7c4a3831358b50a2536 + pristine_git_object: 19b8fd2469069c21ab4b77f62c6b695ba3c0898d docs/models/setuppaymentfeaturequantity.md: id: d15328882840 last_write_checksum: sha1:4f1f0642102d637333d8db0db558ed7d3270b379 @@ -2285,16 +2365,16 @@ trackedFiles: pristine_git_object: e46150aa6597ad09d02461b1438d42eb20ad44cf docs/models/setuppaymentitempriceinterval.md: id: 6337a430ab96 - last_write_checksum: sha1:b663d2d0f2d126f86d87fabbc537e335d050c6c3 - pristine_git_object: 2087c3175fd1aef19a9ba7ab3674b30fccb6daf8 + last_write_checksum: sha1:8dc0e5678b2a0221859198122d57bd0cda7914d8 + pristine_git_object: bfce67601550f16b24bb69be2c62fb51043fcc98 docs/models/setuppaymentondecrease.md: id: 6d4113b4eb77 - last_write_checksum: sha1:b0f2265a18d59b4fe76be480d51e621e3377d25b - pristine_git_object: 1f77bb3aa11e1d1b2725b6e25fceeb5c431f03fa + last_write_checksum: sha1:0bf24f17cdaa4982333094dc508b2184ee030291 + pristine_git_object: e35ae42fa4a7566b81b292e0a697eb42cecb76ac docs/models/setuppaymentonincrease.md: id: fa8cbaeccdec - last_write_checksum: sha1:51d7a45047a57930db101284ae9306778d5282aa - pristine_git_object: 546483389b2445ebcd164757c3fc24f07b48a40a + last_write_checksum: sha1:6ab77cad16e4c3b6cb7ce4c216b4e921b2ff0d6e + pristine_git_object: 6888fb44e13c6ac1a3a20519abbc3c6b6da21c16 docs/models/setuppaymentparams.md: id: d71f82dde273 last_write_checksum: sha1:ea8794fe6f5c47f316256195dcffd9909982659b @@ -2309,24 +2389,24 @@ trackedFiles: pristine_git_object: a7b596ba76c3edb1f48295df565fea57f9e38ac4 docs/models/setuppaymentpriceinterval.md: id: 6dd9488b58f0 - last_write_checksum: sha1:52decddd27ae126eef3b0c7c92d9fc981188335a - pristine_git_object: cfdd9e7effddd9ef752ee10da99fe6bb83972f31 + last_write_checksum: sha1:7341e34bd16262a6ce0fe03f616f352960f45882 + pristine_git_object: ad04914a4bcce2413bdc5404219e4d58c31ac52a docs/models/setuppaymentproration.md: id: 05a134dd45fc last_write_checksum: sha1:5f4b5e93872db5597511d831a6e91b37fcccfc8d pristine_git_object: 03eea5cf24522fae92de2f3edf22afe4dcfbb9eb docs/models/setuppaymentprorationbehavior.md: id: 96ded50f9766 - last_write_checksum: sha1:dc675208709b51015d79c11ecbd6395e6cd7aa59 - pristine_git_object: b79cc25122d2f3e75b5504ac2ab52c1f1ff527cf + last_write_checksum: sha1:1453a79f84609625370edb54c99f5bc60da9fbfc + pristine_git_object: 4b78289cbfa4157f45b92da2382243db67e50abd docs/models/setuppaymentreset.md: id: 1136ec30628f last_write_checksum: sha1:a650b96cb252f1d62f5d6a5e23ebea94959a0224 pristine_git_object: 76cd91f39a87211852c4f8cfffba2ecc0d75437f docs/models/setuppaymentresetinterval.md: id: 38c4d903d5d0 - last_write_checksum: sha1:c45a6d03853d6a37e802f35ab5b5bf29f374e9d7 - pristine_git_object: 6e3c3291a679aff2fe6fe899f354ea0b70bd9c5c + last_write_checksum: sha1:5e333d5badbb195def783ce1511ae61406310b55 + pristine_git_object: d3eb8e68ecc6a857af27ff4918df0bdfb3127a62 docs/models/setuppaymentresponse.md: id: a0fe36809906 last_write_checksum: sha1:0506cb5fd620fb73affa03da445160a245e94fd6 @@ -2341,8 +2421,8 @@ trackedFiles: pristine_git_object: f1184c11fee3ca95c2d182d8952e696f44b3163b docs/models/setuppaymenttierbehavior.md: id: 882d41b17f9b - last_write_checksum: sha1:9624300c291a6abdbfdf023b6e9405d87c03b2e0 - pristine_git_object: a87822d8489977cf4150eebfe1e6ef53ef5b3812 + last_write_checksum: sha1:490c71713c9d35c83684d140e282e6706d219a77 + pristine_git_object: 403b2ac9f33aafaaed978d9958cbbc3d563e795d docs/models/setuppaymentto.md: id: 06660d546541 last_write_checksum: sha1:f8a7cc7213fad6d967cdd1db038ec6eef381ba84 @@ -2353,8 +2433,8 @@ trackedFiles: pristine_git_object: 5bb9f22b2f50cb4c2ba4d8172fdc21b7842cd607 docs/models/subscriptionstatus.md: id: 5f08d32c769a - last_write_checksum: sha1:9daad10a8b01ca11973e1b5e9a35f87ea63bbfaa - pristine_git_object: e4179dfd1a9d9cf69a2829c1d5ad97b9548bb258 + last_write_checksum: sha1:3cfa48d8a7dff1b56bd411ce40c4286ca788de54 + pristine_git_object: 6751a76861dda8aafb85e42820a94f0f302c4dcc docs/models/total.md: id: f4060c3b4657 last_write_checksum: sha1:08c2c14481fcae1bcc1c550d6e2c95f14bb1efb0 @@ -2385,8 +2465,8 @@ trackedFiles: pristine_git_object: 7f44dc945c43a94cd7430babc830c88411354c6f docs/models/updatebalanceinterval.md: id: 1d9c4657f238 - last_write_checksum: sha1:d9ca8e749fd902d267a743daec975852be7833ae - pristine_git_object: bba342bc8878dc61f2d7d3a3c6b306310bce255c + last_write_checksum: sha1:147408360253aa9509de87d41c391d60c8862ed4 + pristine_git_object: b4afc96b118bffa16de417ced419af3984696a68 docs/models/updatebalanceparams.md: id: 8bb20f144794 last_write_checksum: sha1:ec39ca7cb018bbdc4bd0becfc32c6f4dd6f59e6b @@ -2405,12 +2485,12 @@ trackedFiles: pristine_git_object: 78fe38b1840b318e9a42271326249c30be7ae3ae docs/models/updatecustomerbillingcontrolsrequest.md: id: 5c18ec5ef144 - last_write_checksum: sha1:c20ba3a8af94953fa1a381480346d27ad0529bc9 - pristine_git_object: e715ac73cb35146c11e6bf6db939cd3a1465be4e + last_write_checksum: sha1:b0438b946525e8bc97c299211f460acb046dddc9 + pristine_git_object: 96ef36eb666e0a3902c973ed1c92ea36c27b797c docs/models/updatecustomerbillingcontrolsresponse.md: id: e37df91f0df9 - last_write_checksum: sha1:727828070e95dd6404de2c4d02491f1ceb0bffab - pristine_git_object: 8950bc4f42038ff14328222cb81492ccd01a3212 + last_write_checksum: sha1:3952942c32006c9bb9ba45578387416586615085 + pristine_git_object: ceff7c91eb958dc9ceaf64db1535e7a843dc7ef1 docs/models/updatecustomercreditschema.md: id: 9ed2810c66f9 last_write_checksum: sha1:227267de049542b40c7600fff384205cfd8b0d14 @@ -2421,8 +2501,8 @@ trackedFiles: pristine_git_object: 3f8f425694448a3d57a7f45da5185b1ea80cb928 docs/models/updatecustomerenv.md: id: ee8d2224eeb3 - last_write_checksum: sha1:4f41776f1ec13d3cedaefad2f5c913456cdf69f7 - pristine_git_object: 15447cc6e29a6523fe3367bba9bed19fcb10bb5b + last_write_checksum: sha1:2d3ebb5ec7bd7948a7a9b1aa12505b5e725fe3c6 + pristine_git_object: 93eb528d888c1d94f2f243e20d732466bff2d175 docs/models/updatecustomerfeature.md: id: fea7c9019bce last_write_checksum: sha1:6f951a1a22bc74d3c1460f281af27fe01b7940fc @@ -2437,12 +2517,12 @@ trackedFiles: pristine_git_object: 5010263b034bbc3eaddad96700c1088373e6b6d5 docs/models/updatecustomerintervalrequest.md: id: 148680817ee5 - last_write_checksum: sha1:db7d05cc49e59dfdf529bb035474ea200383e25d - pristine_git_object: 3574243b504d51ea32f3ffc1535d7a222f5fa8d2 + last_write_checksum: sha1:fe7148b2ecadcdc72b6ec7d2c86fc9730b81d42b + pristine_git_object: b6e6cc4fd79bb027e4b5f39ee541e5f09a03baa7 docs/models/updatecustomerintervalresponse.md: id: 1dc4aa6e2084 - last_write_checksum: sha1:22ce98772b76a1a75178149d0856e938f510b1f7 - pristine_git_object: 378867d636156a79358809a0e255888423dc0807 + last_write_checksum: sha1:3f5d3bd2b6973514faba2e1602b04296105c36eb + pristine_git_object: 479bb5714d9890ecfa4421845309690f620267a6 docs/models/updatecustomerparams.md: id: 4015e931c04e last_write_checksum: sha1:8a84f9121bac84ad44b20595ab1f9cf3b8d824a6 @@ -2473,24 +2553,40 @@ trackedFiles: pristine_git_object: f604c4499e1547fa5b8d57c1f156d397d4aeff7e docs/models/updatecustomerstatus.md: id: 12e6ef4fbad5 - last_write_checksum: sha1:2cded2d0b887d162dcc8a3b856b5b8e093337064 - pristine_git_object: ed8a2aaa3204d2e6059a244b31379b4740cd2663 + last_write_checksum: sha1:f2aa61050f39ca17d657955339d3f6b81c9b1bc7 + pristine_git_object: 991481f1ae5a7c363ae39cd5f46672548fdde0c7 docs/models/updatecustomersubscription.md: id: 23620d1508c4 last_write_checksum: sha1:9c40c57f10465741cb0f291015c5ab5a289c962b pristine_git_object: 114aba943de596f1b88f17934c479354d739abd4 + docs/models/updatecustomerthresholdtyperequestbody.md: + id: abdb64acf98d + last_write_checksum: sha1:e8bf59424ba1e6407befbfa31569508c41af3828 + pristine_git_object: 45a3eed0c7a07fd7828c994d33e6757138ca31ba + docs/models/updatecustomerthresholdtyperesponse.md: + id: 9139edd2c355 + last_write_checksum: sha1:889f3d456dbe2799b6336d1dabd577cb66e173a4 + pristine_git_object: 22691efd44fb1ea1d967fc058fb2124ce0e86e80 docs/models/updatecustomertype.md: id: ade4d76bc6b0 - last_write_checksum: sha1:fe24ab4f286b788135bd5a2eef54ba192931c40e - pristine_git_object: 1bba5fdc5ccf2638a23acd427b9ed701db73898f + last_write_checksum: sha1:b3872c822e4de81396b9fddc5a49030ff3ad1ff8 + pristine_git_object: f61c05f4e8df8652c6239d6663320da08c19835f + docs/models/updatecustomerusagealertrequestbody.md: + id: 048dc0a2be61 + last_write_checksum: sha1:61770e61771d9b3711a5fa63f60014588dc6da13 + pristine_git_object: c8abd91efd75d0da0c2ba9f282ed2ed44d6d1262 + docs/models/updatecustomerusagealertresponse.md: + id: 7db234ab2581 + last_write_checksum: sha1:78df4f4b709e90bab51a9f7777646024dc157fa6 + pristine_git_object: 1c6e7862b0d9a3d8eb16c96534e3c7922758ddeb docs/models/updateentitybillingcontrolsrequest.md: id: 242c37ce54ce - last_write_checksum: sha1:aa1d39a556b90836af79d6f4c47c12d04aedffe7 - pristine_git_object: 0d1c8f5f64c87d52bf3cf9b7444995b7b3832e1b + last_write_checksum: sha1:8c96dec3e66340d343ec63050c0647d2764f9997 + pristine_git_object: 0125c7a70be7ccc3f24914a3427d8c572a7988a0 docs/models/updateentitybillingcontrolsresponse.md: id: 81d4144625f4 - last_write_checksum: sha1:3fadd5a3dca61bd0cff2ee23975f116053bab3e8 - pristine_git_object: 3cc4ecc1c60a22a57cd6f46854add30d600a2dd5 + last_write_checksum: sha1:f287277db020ccd2052530bebf9b64aab8079c76 + pristine_git_object: 760b0709f0a45a8bffd9a5a65fad7780008f5bce docs/models/updateentitycreditschema.md: id: f48eff237987 last_write_checksum: sha1:0a70801cc38b60162a31657a975d8aabb25b324f @@ -2501,8 +2597,8 @@ trackedFiles: pristine_git_object: 012a9e3a034af846c2c5c5c2fcf0d9befd1a9ca5 docs/models/updateentityenv.md: id: fb351a5af38a - last_write_checksum: sha1:6b6a3d581e16f964b2ed11949e11f72575f2539e - pristine_git_object: df5e9c11ec6ca45e6913af5ef3b1636bacce3c92 + last_write_checksum: sha1:28f989108d327ca7f57448689ef3fe098ae4de08 + pristine_git_object: 8920137285e76a70a5c115b267d3e5d31cd8a4f7 docs/models/updateentityfeature.md: id: 30b1d308d00d last_write_checksum: sha1:993ad4f4aa1ef7ad0ba334265ed0dcfbfde7845d @@ -2541,16 +2637,32 @@ trackedFiles: pristine_git_object: aef36c7c11ca063a7ecdaa72d728bff3fdce4eba docs/models/updateentitystatus.md: id: 3b12d3261c1d - last_write_checksum: sha1:fc8b090bb4620edd0e6c8a97318180fe0d76450a - pristine_git_object: 6a6259482772250caa0ab22ff5f9e0a9d93d9cc6 + last_write_checksum: sha1:c3066481e8b3834a8028c56b595a7a9136734f13 + pristine_git_object: 85d40b65417a2f06d51f1e10541cca44c1585b1a docs/models/updateentitysubscription.md: id: 6b6242a7771b last_write_checksum: sha1:ce6f7693abaed603bc7c0c41572bf95a337ca456 pristine_git_object: c41d09e86e84d272182a57180616fca862dc3033 + docs/models/updateentitythresholdtyperequestbody.md: + id: 9db8438c9a7c + last_write_checksum: sha1:75a2fe8b6aa1ed7c72f006d18b52f8c7621af3bb + pristine_git_object: ef7e1e82f4eeb486674d6032a8f785abe60182db + docs/models/updateentitythresholdtyperesponse.md: + id: f7e69615a268 + last_write_checksum: sha1:0f07ae539ccf6c8d33c47aa0fe109bcfc63af6ce + pristine_git_object: ad6f9f1a763d36d80bc84e5f0ba5c3a6932d1dcb docs/models/updateentitytype.md: id: c63e96d62ca8 - last_write_checksum: sha1:a683e322f2f743930a39dcd27d964917c82831a0 - pristine_git_object: 32f4009a0753446d5f977c1633d303dccc83a7df + last_write_checksum: sha1:2eeca832531a33c7b59607dc6f088c739ed4979e + pristine_git_object: adb0e993b332774500b4e7facaa36c413799ab5b + docs/models/updateentityusagealertrequestbody.md: + id: 0b89a7c9e9e7 + last_write_checksum: sha1:af0e170c400213d375b39133a5170512e99a9a79 + pristine_git_object: 1df705d8005d7b97f6fd4f5b1504ceb302c375af + docs/models/updateentityusagealertresponse.md: + id: 13b4ce2e62c3 + last_write_checksum: sha1:cb78b072a48bd54af9266301c1e7391f6b150c71 + pristine_git_object: 447bc8cec4727f2b60c47cb17f2e9a7d456df152 docs/models/updatefeaturecreditschemarequest.md: id: 44ff205415c8 last_write_checksum: sha1:83a7d17ecd1bf259e95f29a1b56e901f519df814 @@ -2581,28 +2693,28 @@ trackedFiles: pristine_git_object: 846deae07339f3b6ca8b069c0220ef684dc58505 docs/models/updatefeaturetyperequest.md: id: 9dfd1c01e1fe - last_write_checksum: sha1:c29c39e961d308b69828e02d7a8db5dc5f620e6a - pristine_git_object: ccc1bfb836abfeb960f654facb15ff71012e8783 + last_write_checksum: sha1:d993e2a8a7150c1ec34a507f1ec763c3878a39e4 + pristine_git_object: 820adbfdef4578fcaa76d590264b24e521f3ec55 docs/models/updatefeaturetyperesponse.md: id: 0ba0a862dcca - last_write_checksum: sha1:fb11dd3937490037fac83c816866d13881012cbf - pristine_git_object: 36459374751ba3aad0777c2ccba570553a0d7864 + last_write_checksum: sha1:cce928a20c3fccdaa2d05f1cd9f4d24d01ef6eb1 + pristine_git_object: 7ded05a41034ccfa5be6f65118f23d14503dfcdd docs/models/updateplanattachaction.md: id: 606933a404e1 - last_write_checksum: sha1:d81a7652fc07d342634ecd173e8b6f9aed9fa43d - pristine_git_object: ad36f93503838af401489616dfce1916dccb08f4 + last_write_checksum: sha1:f2c212d4b1a791b85c9098386e7583b692ddd8eb + pristine_git_object: b55e8df430d21db509a8840f5e31ec68eda1301e docs/models/updateplanbaseprice.md: id: 61443f3c272a last_write_checksum: sha1:b554cc0015982aca7e2ab60294743f8da9d90290 pristine_git_object: f8cdc4ea1952336cc36aba8e9631925a3c4eb88e docs/models/updateplanbillingmethodrequest.md: id: 9ece57bad5bb - last_write_checksum: sha1:194172cd5d620f3a2f7f0ec3ae3e967702d32d9c - pristine_git_object: 9965276937c54b62becfae3b972bae91490cd003 + last_write_checksum: sha1:cfa1e131cdd0a2fb39157b1f4dfab588dbff01b4 + pristine_git_object: a59277cc10586f7b3937a81ad5d853f5c5e5fa99 docs/models/updateplanbillingmethodresponse.md: id: 1f6a0cc6dd9d - last_write_checksum: sha1:1daaa2ce4ac4ec4dee50322a96f784fb93c7bb55 - pristine_git_object: b374ca2fe3d81b9837259cbd754be60742de24e9 + last_write_checksum: sha1:c43d4eafa56f7696011abfbe2a653e6da459d66f + pristine_git_object: 336643d69469be6447c4b155b0b2582cf2bfc4b0 docs/models/updateplancreditschema.md: id: 00c090e1bf26 last_write_checksum: sha1:38829677cb91237900845a612c71c5341cecb59a @@ -2613,24 +2725,24 @@ trackedFiles: pristine_git_object: b1f0a8a0df11b847fc9798ad43ac5f468238288d docs/models/updateplandurationtyperequest.md: id: a2b05beb83e5 - last_write_checksum: sha1:11307befb98f5b62bd822aa3f544c59a88d66176 - pristine_git_object: adebfb3fc4d77cd2f7604dfadcce42562830a334 + last_write_checksum: sha1:749717a358e65ef0d99188b259bb443fb3baeb5e + pristine_git_object: a5e74c2711474666844909fd7723b7054d3d908e docs/models/updateplandurationtyperesponse.md: id: f7bb7d7c64e1 - last_write_checksum: sha1:f4ae1cfe19a60a83b35acd4f5251dd44a99e06b0 - pristine_git_object: 094a56d4a76d9b1327b6e1125941defde6198b09 + last_write_checksum: sha1:853186bdefa8008db9bbcda97cd1b5c8da7cedd6 + pristine_git_object: a6655d3f8cded4515f58037409612e985ffd3a41 docs/models/updateplanenv.md: id: 7b54ca1834fa - last_write_checksum: sha1:59dfc5df1f8712bed19b33c7953ed726c325219c - pristine_git_object: 9bf11e71cbf7d9dcf7bc6fc2fda970bd9cbec6bf + last_write_checksum: sha1:aeb7637ae4d1aa4d4d9163a90d5525f575781669 + pristine_git_object: 4d2737b1c882b506c026e01fdd63b66b9dbd16d4 docs/models/updateplanexpirydurationtyperequest.md: id: c44c66c550dc - last_write_checksum: sha1:22164f6840d087c3a7a8a73fa79cd846324a9e71 - pristine_git_object: eb290fc03e6bea2612f70e4b9bade7e50f5411ad + last_write_checksum: sha1:67d347bce270f142bf537e3e10095f385d9c0f62 + pristine_git_object: 4cb0c3b037641bed277eb928ae0241f10c3e17c0 docs/models/updateplanexpirydurationtyperesponse.md: id: 3801fddf678d - last_write_checksum: sha1:2e2e826b274e905bb1e75738004335828c74ba01 - pristine_git_object: ec6b74b92b0701d9c7ddce8d9c20ad6977b2ae24 + last_write_checksum: sha1:f7cb607bcb647cb6d995ac8033f9f488aa073554 + pristine_git_object: 0d56ea783952da17b628f6a750aac9a683fae797 docs/models/updateplanfeature.md: id: 0e58961a26f1 last_write_checksum: sha1:940e0421db24f039755d98a9769d68a39dfdb305 @@ -2661,20 +2773,20 @@ trackedFiles: pristine_git_object: 21629fa6207463ffa51a8292ea306694bb3702f0 docs/models/updateplanitempriceintervalrequest.md: id: 4ba82cce682a - last_write_checksum: sha1:c8ef5380b001a3e91ea378b900409e440661729e - pristine_git_object: 29d2f0b6c460c22e783b7806d7fce00ed59c57a6 + last_write_checksum: sha1:e0c021f6936b3a52d8dc5652fe4e8aaa569d5b46 + pristine_git_object: 01a0ae3b5e94f3e919869282da57d52b4ced9f1d docs/models/updateplanitempriceresponse.md: id: ad114972ca0e last_write_checksum: sha1:2276382a193c330dfba34b277b4ed02a71bcd3c0 pristine_git_object: 031e00002ab60c3dff8d4a63d6f732ea0aeee1b3 docs/models/updateplanondecrease.md: id: 02ccbf603191 - last_write_checksum: sha1:b0db07b2c7b2e3c2fe5c07c9f62e1afbb48f26b1 - pristine_git_object: 55590da8bf6e4054fa7050af24eb46b47db849db + last_write_checksum: sha1:2fd6b7131d7b287b55e4933cf56460a282e912dc + pristine_git_object: d21cecf98d8d4914ff936c09a63ab7687292c6fe docs/models/updateplanonincrease.md: id: 764d851d7cca - last_write_checksum: sha1:d42b6434ea9c72ba8c8f8f51faeb17ee30da9e4f - pristine_git_object: 0c2d7187551a45b5d678265295d3521f5286c11c + last_write_checksum: sha1:40bf70c3e175e1d46508f9f250c7b4aa0c2222c8 + pristine_git_object: 04577435bf75320ae14c441d963a401faa3cdc85 docs/models/updateplanparams.md: id: ca9b432d1066 last_write_checksum: sha1:3626993a9b2e8d98b67aeb5b39ae321e6bb90f3a @@ -2689,16 +2801,16 @@ trackedFiles: pristine_git_object: acb2e90916dba40e2bf46027f5a9288945633565 docs/models/updateplanpriceintervalrequest.md: id: 616fa0a68bae - last_write_checksum: sha1:1a1639a4c538495479296e58794345164c2ba0f3 - pristine_git_object: 4461a24c16d64e87b6c3c2ace5c2477f426b3d84 + last_write_checksum: sha1:56b5b2ca99d7033ad6fe94cc4ecc441414e9ab80 + pristine_git_object: 243d5c0620ab4625e02283cce16f978dbe889573 docs/models/updateplanpriceintervalresponse.md: id: fd5b2b703739 - last_write_checksum: sha1:59cc2039b96d0c12ca4013f26e210a4091349383 - pristine_git_object: 69f32259ff488d46da5fbea7f2dc22dc0163d49f + last_write_checksum: sha1:52fc0ea4df6fcaaaf341c737ae7d140db12eca4c + pristine_git_object: 74b3b32e7e92194886ba12877681f19587af3ca7 docs/models/updateplanpriceitemintervalresponse.md: id: d6754f53396d - last_write_checksum: sha1:85e7f34795a8d10bbfdab502835e26ff549e3abe - pristine_git_object: 6abd0556d5f46c9bd9191b2e8175776f298acfae + last_write_checksum: sha1:11791b341094597d0d24d633fb4a44d97e258072 + pristine_git_object: 97de36201b0ea99623c236027ac84971540b259b docs/models/updateplanpricerequest.md: id: 768f8e75b84e last_write_checksum: sha1:62d78bf601e6d1ec1c7898b7e90ad2969f435808 @@ -2713,12 +2825,12 @@ trackedFiles: pristine_git_object: 2d494ae9f039b1cc5e0ce0744f92a4f8207c8eb8 docs/models/updateplanresetintervalrequest.md: id: 7c76042b1bd9 - last_write_checksum: sha1:baecc78ee81e32ab991330160f1ac34719be171c - pristine_git_object: e6e4563ea3e98ed341548c2bba5fcd30148b1e0a + last_write_checksum: sha1:2ffe8a420f674dd4812f921af3a4d8f606c0151a + pristine_git_object: da79d525d03183817af5fd1d86797de83a58e670 docs/models/updateplanresetintervalresponse.md: id: ce0550104677 - last_write_checksum: sha1:859cf2ee39c30e833c8303c7a21deef66a0c9544 - pristine_git_object: 1656ae6671cc4548ebf28a35ebe24b636e96466b + last_write_checksum: sha1:a3e94d39ec09299488530f04d34333aabd42ccae + pristine_git_object: ead7fffc15f6affb3580165d36b3afbd0cd8c601 docs/models/updateplanresetrequest.md: id: e5a7572f3d0c last_write_checksum: sha1:7696cc75c02ddc1747d11d277ae1c39c98f33eed @@ -2741,36 +2853,36 @@ trackedFiles: pristine_git_object: c9c19d637b8d395bf116699d8ea2d732dfa0e707 docs/models/updateplanstatus.md: id: 9fcfb9af1504 - last_write_checksum: sha1:3d9a8b948f1dab91b439b25fe04f05a6cd8d3a40 - pristine_git_object: 3875a75e71fe148db0cc5710ad55a4e86070312f + last_write_checksum: sha1:b5738fed6e1cdc2bca862c277726a4992833d2dd + pristine_git_object: a41b7d601dd6a602601bbaac0211037111d0a983 docs/models/updateplantier.md: id: a0a2771747e8 last_write_checksum: sha1:500cef705b585c0b909d21b60383ca6d0646f5b3 pristine_git_object: bda0ee7ce74ebd4e280de314c55693b14198b20b docs/models/updateplantierbehaviorrequest.md: id: 73b92d1c113f - last_write_checksum: sha1:b1a89235e395df20de6baaba1caadb81d5824f6d - pristine_git_object: 6782a3ee1dc8a1d4d242b46eaa9c7b488e20a8b3 + last_write_checksum: sha1:302dc2df6776fa7a58b457c96b0cdf18b9929684 + pristine_git_object: d686303fb8b882a57f194b1de35bfd2c9ec3c0d2 docs/models/updateplantierbehaviorresponse.md: id: 4cf0cabdcb8d - last_write_checksum: sha1:fcbde197f9ffb6189dc6164ba517dd80c4380521 - pristine_git_object: 1d073f866f2e50853948d8cfed3a796802f3d30a + last_write_checksum: sha1:089f2448f428d3bbf1e4578e9d8da9e2ef15ff7a + pristine_git_object: 24ed2e85cb8ee0323c161b19948b70eb4ce73e20 docs/models/updateplanto.md: id: e0ac738348d2 last_write_checksum: sha1:44502fbd923cf1209624e883c4cb06c757171410 pristine_git_object: de7349c14ecaa21bc7ec57d5c2f6667537ff0e46 docs/models/updateplantype.md: id: edd98356600c - last_write_checksum: sha1:b0a891101d766bd45726dd49d9e48dd4b8948055 - pristine_git_object: e058f74c3f1ca285380a040c69c6a208a90d503f + last_write_checksum: sha1:4c871c437a31b5f2547081f5b0f5eaee1f70ca19 + pristine_git_object: b9d2352c615bcb39513c4cb64ff5ea5278814710 docs/models/updatesubscriptionparams.md: id: df1b06618f8b - last_write_checksum: sha1:16496d5a4b9dbe46025405f29265b2852df35e90 - pristine_git_object: 5863c4f1e99ac8b3d0425be69dc215cd3184d3cb + last_write_checksum: sha1:6af36f14afab7a845be5ce6ebce9896a921f2e70 + pristine_git_object: 3e48baa7c98f173dba9e81cc3eda5f08c519e4cd docs/models/usagemodel.md: id: 1e12a2a8fc52 - last_write_checksum: sha1:0aae39e704f2d84d1ca66c0ff3e0bbdd5dba61cf - pristine_git_object: 1c478ab31dc35e34bc4fc7eee7c0078f31ceb35b + last_write_checksum: sha1:6e6803b39c0b8d88be49568c7a1bbcf8b4dba4c1 + pristine_git_object: 04bed5b0edb595538e12de13f5b395a74ecdbe2e docs/models/utils/retryconfig.md: id: 4343ac43161c last_write_checksum: sha1:562c0f21e308ad10c27f85f75704c15592c6929d @@ -2785,8 +2897,8 @@ trackedFiles: pristine_git_object: 2d96280cb4f0e2da0ac93bb8c9ca2ea1e515b92f docs/sdks/billing/README.md: id: dc915331dd9d - last_write_checksum: sha1:cc77d117d36e0fccaf5a3a2fd26dae271707638f - pristine_git_object: e68a5a424f93179a8b754ad53a6dabc146048af7 + last_write_checksum: sha1:1ebecb2f17f53704d40cc64d2cc24021f192f1c0 + pristine_git_object: 3f7a8648ac93ed289da197b41be2772b3281cfd1 docs/sdks/customers/README.md: id: 9332759cffc2 last_write_checksum: sha1:6afa5d58ebf54f5c89b04add33684673976d8a82 @@ -2817,8 +2929,8 @@ trackedFiles: pristine_git_object: 3e38f1a929f7d6b1d6de74604aa87e3d8f010544 pylintrc: id: 7ce8b9f946e6 - last_write_checksum: sha1:245d4be24271141847add79be121c9b05f7cd993 - pristine_git_object: 32f489a86f8bdc09713a8cf9647393a3005f8b05 + last_write_checksum: sha1:df60ab94013c483f71ff98c51f349a016c15a117 + pristine_git_object: 5650f49e06066f8a1338afdbc0e155f5277152df pyproject.toml: id: 5d07e7d72637 last_write_checksum: sha1:fd0badc54d2c51c633d02cacda0e7a9fd5abaa00 @@ -2845,8 +2957,8 @@ trackedFiles: pristine_git_object: 3e604651c1eae73d815b276806e73b2f1334bb79 src/autumn_sdk/_version.py: id: a98babfdf4fc - last_write_checksum: sha1:aaca892b083d842552de2cf356665518d2b0d2c5 - pristine_git_object: 0901a0364951c52154c73af4e9a14e2612d054a9 + last_write_checksum: sha1:4ff651eb790585bc44591e3e253c60a52f5e49ea + pristine_git_object: af61685f99a494e0162b7b20f822b514788ac030 src/autumn_sdk/balances.py: id: 0a15be654dad last_write_checksum: sha1:46067ffca43331f7efed02bf80a3102557f69400 @@ -2857,8 +2969,8 @@ trackedFiles: pristine_git_object: b8da96bd89ddafaedb32f4ecac64c27027fcca79 src/autumn_sdk/billing.py: id: e6cffdbf2221 - last_write_checksum: sha1:19fb0b466cba919e5e17a8ee25224d320b8e136c - pristine_git_object: 20bf99967845b3d3b036216e1c3b23da887a0253 + last_write_checksum: sha1:8f146a83f1c427e423ae2e0a43d2b20a6d63a803 + pristine_git_object: 0c532d365672a836909daee34a99fe25fe71c0db src/autumn_sdk/customers.py: id: 5c5a0a07a433 last_write_checksum: sha1:c845e37685e7d5b126c2c0d085d675b3befd527c @@ -2869,8 +2981,8 @@ trackedFiles: pristine_git_object: f6b57b6c0194f874f0ba24d8f2c0175669189983 src/autumn_sdk/errors/__init__.py: id: 242853123cf2 - last_write_checksum: sha1:1c4f4e0181a6598b531c2621340548b003df6e2a - pristine_git_object: 3c83f2947cf86f28759fe6a53700d40dd5bc7be7 + last_write_checksum: sha1:3185fbf12ed61a9845f94127cb110bce8f7f8f66 + pristine_git_object: 712bfe798f42e653816668fe6a5f13e2957f12a2 src/autumn_sdk/errors/autumndefaulterror.py: id: 4c2d6cac83ee last_write_checksum: sha1:f45d500766d652f899f6cacbfaab5669e190f19e @@ -2901,180 +3013,180 @@ trackedFiles: pristine_git_object: 89560b566073785535643e694c112bedbd3db13d src/autumn_sdk/models/__init__.py: id: bcf3802243ff - last_write_checksum: sha1:9b8562f6c752838c9bcec68ce099993a049671a3 - pristine_git_object: 0127c6be31631977ece679d8e896ed739df0dcfa + last_write_checksum: sha1:ff258b03469e08db7b70dd3c780b34cad56667e4 + pristine_git_object: b397b6b41232d533068c68ad67c5e4f3b30ccded src/autumn_sdk/models/aggregateeventsop.py: id: 01321099f2a5 - last_write_checksum: sha1:d5605f1074da718e5da0195eb951ffc1c9d0fc20 - pristine_git_object: b62e4c6171ad275106314421884848714645ae13 + last_write_checksum: sha1:eef6ab478132141f11c820ee80cbacaa4bc74870 + pristine_git_object: c6a100bbd09941de44de909199a510fc0f8274d3 src/autumn_sdk/models/attachop.py: id: ebb59e06476c - last_write_checksum: sha1:400be2baf57968dec4da86b47890d48bb1a763c7 - pristine_git_object: 434a571efced4fa2a36c4d1cc134d3d573da5e01 + last_write_checksum: sha1:bab7654856f352189edd8a71be3534b3b1270b52 + pristine_git_object: 12b3c3c9c6dce58f1adea685c1373c298e63286f src/autumn_sdk/models/balance.py: id: a6354d7c4b97 - last_write_checksum: sha1:dde97ce288519d7caba988d09f419bb0a79719f9 - pristine_git_object: f08b03da984fb8dd469b339054ceb805e122a3b1 + last_write_checksum: sha1:de11fef2010f8b21ece416a3f69c0ec9cfdaab4e + pristine_git_object: 9e4d3d82cb0fa84ec6354e19107bae5297796fd7 src/autumn_sdk/models/billingupdateop.py: id: a2f17c75cfd3 - last_write_checksum: sha1:fa6e28eee9bd7650ad19df1041cb4469e6a55362 - pristine_git_object: 944115475eaff185903df3a54961a9cbf51b9a49 + last_write_checksum: sha1:1bdebd3dff7ce5d6abe5c067ffc5b3f57eb88afc + pristine_git_object: 52804986febccc71330da173cc9156a672e742ea src/autumn_sdk/models/checkop.py: id: 31c2f84723c6 - last_write_checksum: sha1:eab1a08cedd5cd932e5cf2e7c29ed1569500495a - pristine_git_object: 0d0e2e97721b336211a33b28b6a166abf980ebcf + last_write_checksum: sha1:6afa24e310c28a61a76df8d6facf30c1deb04168 + pristine_git_object: 81008db9ba697d832b045ee8f0181823451f238d src/autumn_sdk/models/createbalanceop.py: id: 27daf4da75bf - last_write_checksum: sha1:336706059035f2c2de5f3fd259a3079ef2e5efe3 - pristine_git_object: 5f8b89b4b876a9c96f3e4ee8cc5d642d709cf496 + last_write_checksum: sha1:6d5d0bec52c288ecce41f5aabda3954f64645d31 + pristine_git_object: 674607646f18d0d09b25609c80ed43fe3f3a9bb0 src/autumn_sdk/models/createentityop.py: id: bf9521c0cfec - last_write_checksum: sha1:e1c2c16053fa252c7c6eef13e6e00b82325259e7 - pristine_git_object: 006b843c49c037a15a1b8d5496d36280cd315ab8 + last_write_checksum: sha1:e71077c85ce6ebc5ebac3a7be7ab0de4fdd15b00 + pristine_git_object: 29bc6efc354562e569195550ccc1eb7c7ac9396e src/autumn_sdk/models/createfeatureop.py: id: 68487033fbe5 - last_write_checksum: sha1:7700b9041190ce954caa13d6981c0c1460e5ee20 - pristine_git_object: 35a43e4dd6ea06d0ffbbde5ffaead959ce207632 + last_write_checksum: sha1:a6af9e8d51d99eeff68bed6c8d9f0915c6fdd54e + pristine_git_object: b6be52d60ce2ba4330e8de7a1dc0976677471e4d src/autumn_sdk/models/createplanop.py: id: 077e6c7db2ad - last_write_checksum: sha1:4a1bc73c0314c2614a0fae7e486469d987b8a0a8 - pristine_git_object: 68ffef8988f54d8c18b5d0a92db6fae4f16b2479 + last_write_checksum: sha1:ef6cedb4fe855e07f446707e725d0d8dcbd447ef + pristine_git_object: 3188aa943169ea519cd6aa365e3a254fcc403332 src/autumn_sdk/models/createreferralcodeop.py: id: 2f5f7b136c39 - last_write_checksum: sha1:aee36f911153fc0dab9fd096dbb82d1083a61d28 - pristine_git_object: cd3ad48900dab5fd4b07db41d62388e68a1818b2 + last_write_checksum: sha1:0a200e513da7332ed334d61d4ab60f0fecebbad2 + pristine_git_object: 60bb089e74dc86d08cc861e85afa93a1a9c6955f src/autumn_sdk/models/customer.py: id: 8ed0174f7272 - last_write_checksum: sha1:6b4a7655b21dd6a4f35356283411005341e0d69e - pristine_git_object: e041b7cf16303815fb6d2f1c75a9700e05887edf + last_write_checksum: sha1:3ddbea2db4a76ac98ac5884f6cb0602cdcbadd43 + pristine_git_object: 52b3cda1122ae1cbdfa275d160de11cc296638e4 src/autumn_sdk/models/customerdata.py: id: 9d88118f2123 - last_write_checksum: sha1:a42f21606d9e2499a63588f909714e723eafd384 - pristine_git_object: 192060327ebe9828b5a2a0556b18585c01911c93 + last_write_checksum: sha1:5205991e85a0dc3e85fc2fdeb55fe300aba31027 + pristine_git_object: 78cb71f16bdcf60d26f0beb1aeb37a309caa035d src/autumn_sdk/models/deletebalanceop.py: id: 97d06f64852a - last_write_checksum: sha1:d8e94ba342b57ee31d54cb7c2d45bfb2b840e095 - pristine_git_object: e93512ba6bd77160a145debfa5131fc372538c75 + last_write_checksum: sha1:47587f11ca64dd8f9613e1be744740b81a6783a4 + pristine_git_object: 8d28358187140887712b7c908df799b21f01f7c2 src/autumn_sdk/models/deletecustomerop.py: id: dc7a1e2cc90b - last_write_checksum: sha1:8b345d4715c2fa291674b86287439e824cd479ce - pristine_git_object: f731ec176a58301941259ea8c4b36353703802bb + last_write_checksum: sha1:830f779b77d3d232450c6a1d16691384caf534a8 + pristine_git_object: 360a0ca57ef52d3fbffe68234c71fb4c7378ed78 src/autumn_sdk/models/deleteentityop.py: id: f875e07e0401 - last_write_checksum: sha1:a596e590cb293b64f97549972cb674f0ea6661c0 - pristine_git_object: 994654d79145e1b31ccf1bb2400732e13060c11c + last_write_checksum: sha1:84736474b4e695a5c3f22b1a13401a21ef1e8953 + pristine_git_object: 3aec2ed90dff25e3a63c8bfc949fb76c34cb144a src/autumn_sdk/models/deletefeatureop.py: id: 2cde77bc8684 - last_write_checksum: sha1:eb0e3de24676ed9ca638050e753adcf77d9e9273 - pristine_git_object: d16ff515bd2fd4ae2c67c7b3099b9bbc981e966d + last_write_checksum: sha1:44c38cc8b9873f1bdb161b80d906c601353741c2 + pristine_git_object: 1cb236ef47598596874e93325f6d21b81f90117c src/autumn_sdk/models/deleteplanop.py: id: 4dd8168b93d3 - last_write_checksum: sha1:b66060053ba95103b6f9c21583c837aff5bf4f5b - pristine_git_object: 8399927609dfaba9ca603a77e3f85ada3861d2f2 + last_write_checksum: sha1:ce4b60376e96d39301d8d2cd56d6588dbdce973d + pristine_git_object: 8d60176d44a69f0b5d4f152bd7acf52f0e43cfba src/autumn_sdk/models/finalizelockop.py: id: 8ae2484b0916 - last_write_checksum: sha1:e8db3e126ea7a98e56c992067ad838474aaaea90 - pristine_git_object: 5ca71e3f0ae7d38ab7d8c37d6a1dc3e4a6b04ef5 + last_write_checksum: sha1:732c896663074ddde0026cd9940c8c24635cfa00 + pristine_git_object: 7fe4a83808fec9903c805bb9a423e5d0322f726e src/autumn_sdk/models/getentityop.py: id: 6a624594b41f - last_write_checksum: sha1:fe0810227dc692312a3eb0a5b8ee5510f357c6c1 - pristine_git_object: 956fe9aac5639a9e06aa43453fb523da4e026b36 + last_write_checksum: sha1:1afed1a14318807b72c294316fcfd036cc24a326 + pristine_git_object: 828874eab4d48617b285d060e753714d5a012bcd src/autumn_sdk/models/getfeatureop.py: id: 72b158789497 - last_write_checksum: sha1:89a9f44a1af9b36cf229f80000cae16054da17a5 - pristine_git_object: 63aec0714ea5d7f98ffdb2d7125d0b9db6fe8537 + last_write_checksum: sha1:280bb7e8829a45480171f13f82925f2eae111787 + pristine_git_object: 6a02216300e81c94571a208bc78ca3381b638c2a src/autumn_sdk/models/getorcreatecustomerop.py: id: acfac0d7be14 - last_write_checksum: sha1:6b826257afb8592b3d5ac41d8deb9837e18e13ac - pristine_git_object: a26722bad46da59260e383d531d57d854fb03ecf + last_write_checksum: sha1:67f6e3e9e748c3610094819df6ffb895623a4c39 + pristine_git_object: c9070631520968855a271dafe454ce083df842b1 src/autumn_sdk/models/getplanop.py: id: 590fb77ac88d - last_write_checksum: sha1:eafc7c00dd453d1ddf25c531a1ef44c38267219b - pristine_git_object: 03d634d43cb00c85bd0fadeaa0491585193a619e + last_write_checksum: sha1:5baf6dcfbdd99fe10f0ceda6e1da09744f306ef6 + pristine_git_object: 1c7980ff786736806ff290f2f5323b6289ff1fe8 src/autumn_sdk/models/internal/__init__.py: id: 2906fe7f2cde - last_write_checksum: sha1:1905b58b74ecc52346d8f5c24ded2b6d6e1dad4a - pristine_git_object: ceabb1b83cb7cc1e21e65639c69c30749d36f6e2 + last_write_checksum: sha1:5beaf7e5a713d3eead465c6edcfdeb7f8076175c + pristine_git_object: e7070a1222c2ed3fa3f6f54e6f122cae7ff9a449 src/autumn_sdk/models/internal/globals.py: id: 4e33eb99f463 - last_write_checksum: sha1:a201fca661b6d4cc20fb9e3d2f6c1221b78a43d6 - pristine_git_object: 72107ef2601ee3f268b011e90c488b8a69512588 + last_write_checksum: sha1:789b88114444bbdba50c5f94259c79a8d8df2012 + pristine_git_object: 9b32efedd583899e465fab5f305b08fb51a14eff src/autumn_sdk/models/listcustomersop.py: id: d7074740b8b0 - last_write_checksum: sha1:e3e50114c2141b4265190928e8b2788d1c775ca3 - pristine_git_object: 94dfc819f354d92464e52c1ee2bf856467b270b3 + last_write_checksum: sha1:1fb9535206d3ef16ed66fb77f4991646dbd3ba0a + pristine_git_object: 701f0a5aa121ba18c7c07fed0ea1bdbcce6f6d10 src/autumn_sdk/models/listeventsop.py: id: 751b0200d91d - last_write_checksum: sha1:58643d5dedb4f9f82aa8ddd15ac9f4bb284189cd - pristine_git_object: 98b7f60ad8e09f596765297fb8c7c698781933b6 + last_write_checksum: sha1:b858631e46a26812ce801465327c2c1c13ba5cf3 + pristine_git_object: 52845e969536256171c956511d3d149bf11e3261 src/autumn_sdk/models/listfeaturesop.py: id: 95f88614bd8e - last_write_checksum: sha1:983c9bab14d984cdc250fd5822ca98dc373c3cae - pristine_git_object: 710a3e36719b112a82fa98903073bb090f72c2ec + last_write_checksum: sha1:25371d9c007ba3a95672f923f48848e36191eeef + pristine_git_object: b6731d7f7a630c7fc7fabb4f72d626a02fb9166a src/autumn_sdk/models/listplansop.py: id: fdf892c403f4 - last_write_checksum: sha1:d46b12a22952de5878253363f0d83b79c972432b - pristine_git_object: bd090552f6a528ed73eae357c82556edda7571ca + last_write_checksum: sha1:1943ae6de4ef7e01dd7c431eee1f370c0df9540d + pristine_git_object: 55bab58d8d42ebec2f74feb34150af66ef855fa0 src/autumn_sdk/models/multiattachop.py: id: dfdf7952c870 - last_write_checksum: sha1:80f78c9baae1f0bbb0b3d8195a55a9b68a0e9b70 - pristine_git_object: bb5ed660bb324addd80ca4532837ec7327a2188e + last_write_checksum: sha1:a0cdc68ee60d1598d35bcfef46b0eb7d93fd5cad + pristine_git_object: 2b48c43afdbc6289a27960efa9e8ba9b4b4ce6b6 src/autumn_sdk/models/opencustomerportalop.py: id: 004cc9a6466f - last_write_checksum: sha1:b190ddc8e45b3198fa12ae5f49deb9d8cac20a5e - pristine_git_object: 8383509aa89e0c11988baef8e424f5ae682d5c8e + last_write_checksum: sha1:16d5efefb01ca9588607ac1f4d847a9685d96308 + pristine_git_object: bbf47f20ccf883e55c503f03a985f11e62e6b3e8 src/autumn_sdk/models/plan.py: id: f85c4e07540d - last_write_checksum: sha1:5225479c27d1013d35008ade2fe906918724eeef - pristine_git_object: 20844c9b66feb17e013bb74dfc01d2bd845083d3 + last_write_checksum: sha1:9c36e49961c10f98bc69bcdc1788607f9f25813b + pristine_git_object: 56a42a2d22eb3a21547ee87870f73f7c247aefa3 src/autumn_sdk/models/previewattachop.py: id: 2b361be4bfa8 - last_write_checksum: sha1:43579c12cb7281ab40052160e18303667bf39a4b - pristine_git_object: 60df178cfb8963b82340e7d14029b9532519a7d8 + last_write_checksum: sha1:5bb1e64fd73f058a43b900c665809c0dc952cb27 + pristine_git_object: dacc7ba980a8f9701af6f1534227cfea36765d44 src/autumn_sdk/models/previewmultiattachop.py: id: 963ffcd646a4 - last_write_checksum: sha1:4410aca7163a9a25a7eae4ce815204e00bc63036 - pristine_git_object: 187099042034d8ea1296f7b3e16524c0acf201c7 + last_write_checksum: sha1:a4f24fcc5db4c927f48a32688652117a890ea17f + pristine_git_object: 06df124f54e5a536317aeda045079cbb83a79534 src/autumn_sdk/models/previewupdateop.py: id: 081d5f08508d - last_write_checksum: sha1:6b328c94ef12dfc233e975fb46a94353c0c992ef - pristine_git_object: 2c257a59efad61dbfd075e917b50089e7412986c + last_write_checksum: sha1:c526e015b5085720840a5e38e2c8e41968ed9f16 + pristine_git_object: 0602a85b6efe3b2cba082acb224da1daf45ba491 src/autumn_sdk/models/redeemreferralcodeop.py: id: 0abd7bfae718 - last_write_checksum: sha1:df1248aa8876e069c8094cba0541acc4911db620 - pristine_git_object: 2e67e9b952a5107efcb3d79f2d9b6b63c06b4030 + last_write_checksum: sha1:4cb2bfcd36fbd8fa3ab02ac090083e0eabcc0008 + pristine_git_object: 6f1c6e085581e47da9aac794a0e8a0e5d6cfd595 src/autumn_sdk/models/security.py: id: 27d01b755fbe last_write_checksum: sha1:e5ac2e52ed9c2db46d4989c4744c230dd12bdf01 pristine_git_object: aa686dd6f85ae1e27450392fcfe02527adfe8e61 src/autumn_sdk/models/setuppaymentop.py: id: 603339ee67e3 - last_write_checksum: sha1:4944e08928a54c16fdafc93ebadd0b5fce2da229 - pristine_git_object: 195e95672325f6748f852a838e4f6953c8c2bccf + last_write_checksum: sha1:d45e699c2ff5a888cac83c2cd1d4a8d56bf8d95f + pristine_git_object: a3f5643d553197932dfe2a3b9ce8f8ad17c69170 src/autumn_sdk/models/trackop.py: id: 2a744315e781 - last_write_checksum: sha1:e9f1d9c4bbfc47eb5c942f8cfca4a760818bf79d - pristine_git_object: e3581f4139bb985c5c5c058754288bc340d022d1 + last_write_checksum: sha1:adc81955c49e19b435f3d774bcd08ef705a4ba94 + pristine_git_object: a0e92d5bcced974143d3dd24664136a65cf63cf1 src/autumn_sdk/models/updatebalanceop.py: id: cd80d90d4cae - last_write_checksum: sha1:7b6b881cc82b937e93cfd58f17d7f3d83bdb39f1 - pristine_git_object: d69bad2bc308f31e4983cf5e2cd0639cedd9c9aa + last_write_checksum: sha1:ad346cbd8f6ee5e62996b3387ab652fec7e2dc8b + pristine_git_object: 39682e946abe0062ac2b57ed659a48bdb412e7ae src/autumn_sdk/models/updatecustomerop.py: id: 28b9d5b59bae - last_write_checksum: sha1:bcf63fac8c44c1660dae509483ebfe90f9f2ee54 - pristine_git_object: 677b65cdff9d49eaad1172d21b492b91df24293d + last_write_checksum: sha1:f2f546a4d870c3ecbff3eba59ff90f49fe382390 + pristine_git_object: 0b4d17dba201395fccfadd07c8456d19da6914be src/autumn_sdk/models/updateentityop.py: id: a49305af1e2e - last_write_checksum: sha1:a8b07e324b551a3e4dc50efb0b5fbb343d3db6cb - pristine_git_object: 3f7d90a4b52f6957fbdd0f5ed3d0b00a88978ea2 + last_write_checksum: sha1:a88c0b9c38898ae078f806bc0935da16816d1ab2 + pristine_git_object: 8ac0c68348f2c86e60f2a6971399fd1bd9a822ba src/autumn_sdk/models/updatefeatureop.py: id: 2fdfed4aa2f2 - last_write_checksum: sha1:67e6bcbdf14980e19787d10e0ab42485acff0862 - pristine_git_object: 9507c8601dca2cefbc0b8b7d423a38a8272fe580 + last_write_checksum: sha1:38437d262ccbb2a4ad23ef30537bbf2701e7a21f + pristine_git_object: d21c033e2dc2425ce2b98c3b0f77b7017c37ab02 src/autumn_sdk/models/updateplanop.py: id: 753ddf45ca40 - last_write_checksum: sha1:921296bbddba4def95223eb8eae068da86820ce1 - pristine_git_object: bec31e36dab1b7ec384d1a11302aec8dbdddcba6 + last_write_checksum: sha1:190ee64258ef11362f040b574319d3132c2f9f41 + pristine_git_object: 0ba234f795d704061dad65554698f702af96ff7d src/autumn_sdk/plans.py: id: cf1ebabb687c last_write_checksum: sha1:44a5f21bcc8df9df4590ec75c3296e7c0b726323 @@ -3089,8 +3201,8 @@ trackedFiles: pristine_git_object: 4fde94822b61d8a600b721fcbda99116c0e7a6ad src/autumn_sdk/sdk.py: id: 9e733b372628 - last_write_checksum: sha1:0ef7a3d1e8091af849e512c3bddbfface4e8f827 - pristine_git_object: 8e3f647ff341dba96c3dbdd9bdaa5b177bca794b + last_write_checksum: sha1:a22754bfcc074a201e15b76ba4982128d16f5251 + pristine_git_object: 285140a4af75927b60618366985919de071d720e src/autumn_sdk/sdkconfiguration.py: id: e65df2e44fc0 last_write_checksum: sha1:233b710dff940202f00e389e0c8fa6a33f6ae7b4 @@ -3105,8 +3217,8 @@ trackedFiles: pristine_git_object: a9a640a1a7048736383f96c67c6290c86bf536ee src/autumn_sdk/utils/__init__.py: id: e8add1cf9e39 - last_write_checksum: sha1:1970816f2234ecb8785798240b0edced961de971 - pristine_git_object: 0498cb8dabf249b39609f81fb10cddc30f1b78b5 + last_write_checksum: sha1:a1f6ae620fb6a3ccc30e99b427e49a0c8be463af + pristine_git_object: 15394a08a7e30033d319e44dd5734664ddb587e5 src/autumn_sdk/utils/annotations.py: id: 781e615bef82 last_write_checksum: sha1:a4824ad65f730303e4e1e3ec1febf87b4eb46dbc @@ -3115,18 +3227,14 @@ trackedFiles: id: ae6f3bef0a07 last_write_checksum: sha1:c721e4123000e7dc61ec52b28a739439d9e17341 pristine_git_object: a6c52cd61bbe2d459046c940ce5e8c469f2f0664 - src/autumn_sdk/utils/dynamic_imports.py: - id: 88e374972bee - last_write_checksum: sha1:a1940c63feb8eddfd8026de53384baf5056d5dcc - pristine_git_object: 673edf82a97d0fea7295625d3e092ea369a36b79 src/autumn_sdk/utils/enums.py: id: 6274197449b4 last_write_checksum: sha1:bc8c3c1285ae09ba8a094ee5c3d9c7f41fa1284d pristine_git_object: 3324e1bc2668c54c4d5f5a1a845675319757a828 src/autumn_sdk/utils/eventstreaming.py: id: 987e3dcdf32d - last_write_checksum: sha1:620d78a8b4e3b854e08d136e02e40a01a786bd70 - pristine_git_object: 3bdcd6d3d4fc772cb7f5fca8685dcdc8c85e13e8 + last_write_checksum: sha1:ffa870a25a7e4e2015bfd7a467ccd3aa1de97f0e + pristine_git_object: f2052fc22d9fd6c663ba3dce019fe234ca37108b src/autumn_sdk/utils/forms.py: id: 62fce1e7404c last_write_checksum: sha1:0ca31459b99f761fcc6d0557a0a38daac4ad50f4 @@ -3153,8 +3261,8 @@ trackedFiles: pristine_git_object: 1de32b6d26f46590232f398fdba6ce0072f1659c src/autumn_sdk/utils/retries.py: id: 7b3d494f85a1 - last_write_checksum: sha1:471372f5c5d1dd5583239c9cf3c75f1b636e5d87 - pristine_git_object: af07d4e941007af4213c5ec9047ef8a2fca04e5e + last_write_checksum: sha1:5b97ac4f59357d70c2529975d50364c88bcad607 + pristine_git_object: 88a91b10cd2076b4a2c6cff2ac6bfaa5e3c5ad13 src/autumn_sdk/utils/security.py: id: 205e1b4aa7c9 last_write_checksum: sha1:435dd8b180cefcd733e635b9fa45512da091d9c0 @@ -3661,7 +3769,7 @@ examples: application/json: {"customer_id": "cus_123", "plans": [{"plan_id": "pro_plan"}, {"plan_id": "addon_seats", "feature_quantities": [{"feature_id": "seats", "quantity": 5}]}], "redirect_mode": "if_required"} responses: "200": - application/json: {"customer_id": "", "line_items": [{"display_name": "Percival_Towne81", "description": "as unfortunately wherever crest", "subtotal": 1273.44, "total": 7630.04, "plan_id": "", "feature_id": null, "quantity": 1062.24}], "subtotal": 20, "total": 20, "currency": "usd", "incoming": [{"plan_id": "", "feature_quantities": [], "effective_at": 1983.1}], "outgoing": []} + application/json: {"customer_id": "", "line_items": [{"display_name": "Percival_Towne81", "description": "as unfortunately wherever crest", "subtotal": 1273.44, "total": 7630.04, "plan_id": "", "feature_id": null, "quantity": 1062.24}], "subtotal": 20, "total": 20, "currency": "usd", "incoming": [{"plan_id": "", "feature_quantities": [], "effective_at": 1983.1, "canceled_at": 2257.08, "expires_at": 7910.67}], "outgoing": []} multiAttach: speakeasy-default-multi-attach: parameters: diff --git a/others/python-sdk/pylintrc b/others/python-sdk/pylintrc index 32f489a86..5650f49e0 100644 --- a/others/python-sdk/pylintrc +++ b/others/python-sdk/pylintrc @@ -459,8 +459,7 @@ disable=raw-checker-failed, consider-using-with, wildcard-import, unused-wildcard-import, - too-many-return-statements, - redefined-builtin + too-many-return-statements # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/others/python-sdk/src/autumn_sdk/_version.py b/others/python-sdk/src/autumn_sdk/_version.py index 0901a0364..af61685f9 100644 --- a/others/python-sdk/src/autumn_sdk/_version.py +++ b/others/python-sdk/src/autumn_sdk/_version.py @@ -5,8 +5,8 @@ __title__: str = "autumn-sdk" __version__: str = "0.4.18" __openapi_doc_version__: str = "2.2.0" -__gen_version__: str = "2.866.2" -__user_agent__: str = "speakeasy-sdk/python 0.4.18 2.866.2 2.2.0 autumn-sdk" +__gen_version__: str = "2.824.1" +__user_agent__: str = "speakeasy-sdk/python 0.4.18 2.824.1 2.2.0 autumn-sdk" try: if __package__ is not None: diff --git a/others/python-sdk/src/autumn_sdk/billing.py b/others/python-sdk/src/autumn_sdk/billing.py index 20bf99967..0c532d365 100644 --- a/others/python-sdk/src/autumn_sdk/billing.py +++ b/others/python-sdk/src/autumn_sdk/billing.py @@ -1418,6 +1418,12 @@ def update( subscription_id: Optional[str] = None, cancel_action: Optional[models.BillingUpdateCancelAction] = None, no_billing_changes: Optional[bool] = None, + recalculate_balances: Optional[ + Union[ + models.BillingUpdateRecalculateBalances, + models.BillingUpdateRecalculateBalancesTypedDict, + ] + ] = None, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -1439,6 +1445,7 @@ def update( :param subscription_id: A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. :param cancel_action: Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. :param no_billing_changes: If true, the subscription is updated internally without applying billing changes in Stripe. + :param recalculate_balances: Controls whether balances should be recalculated during the subscription update. :param retries: Override the default retry configuration for this method :param server_url: Override the default server URL for this method :param timeout_ms: Override the default request timeout configuration for this method in milliseconds @@ -1473,6 +1480,9 @@ def update( subscription_id=subscription_id, cancel_action=cancel_action, no_billing_changes=no_billing_changes, + recalculate_balances=utils.get_pydantic_model( + recalculate_balances, Optional[models.BillingUpdateRecalculateBalances] + ), ) req = self._build_request( @@ -1561,6 +1571,12 @@ async def update_async( subscription_id: Optional[str] = None, cancel_action: Optional[models.BillingUpdateCancelAction] = None, no_billing_changes: Optional[bool] = None, + recalculate_balances: Optional[ + Union[ + models.BillingUpdateRecalculateBalances, + models.BillingUpdateRecalculateBalancesTypedDict, + ] + ] = None, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -1582,6 +1598,7 @@ async def update_async( :param subscription_id: A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. :param cancel_action: Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. :param no_billing_changes: If true, the subscription is updated internally without applying billing changes in Stripe. + :param recalculate_balances: Controls whether balances should be recalculated during the subscription update. :param retries: Override the default retry configuration for this method :param server_url: Override the default server URL for this method :param timeout_ms: Override the default request timeout configuration for this method in milliseconds @@ -1616,6 +1633,9 @@ async def update_async( subscription_id=subscription_id, cancel_action=cancel_action, no_billing_changes=no_billing_changes, + recalculate_balances=utils.get_pydantic_model( + recalculate_balances, Optional[models.BillingUpdateRecalculateBalances] + ), ) req = self._build_request_async( @@ -1704,6 +1724,12 @@ def preview_update( subscription_id: Optional[str] = None, cancel_action: Optional[models.PreviewUpdateCancelAction] = None, no_billing_changes: Optional[bool] = None, + recalculate_balances: Optional[ + Union[ + models.PreviewUpdateRecalculateBalances, + models.PreviewUpdateRecalculateBalancesTypedDict, + ] + ] = None, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -1725,6 +1751,7 @@ def preview_update( :param subscription_id: A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. :param cancel_action: Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. :param no_billing_changes: If true, the subscription is updated internally without applying billing changes in Stripe. + :param recalculate_balances: Controls whether balances should be recalculated during the subscription update. :param retries: Override the default retry configuration for this method :param server_url: Override the default server URL for this method :param timeout_ms: Override the default request timeout configuration for this method in milliseconds @@ -1760,6 +1787,9 @@ def preview_update( subscription_id=subscription_id, cancel_action=cancel_action, no_billing_changes=no_billing_changes, + recalculate_balances=utils.get_pydantic_model( + recalculate_balances, Optional[models.PreviewUpdateRecalculateBalances] + ), ) req = self._build_request( @@ -1848,6 +1878,12 @@ async def preview_update_async( subscription_id: Optional[str] = None, cancel_action: Optional[models.PreviewUpdateCancelAction] = None, no_billing_changes: Optional[bool] = None, + recalculate_balances: Optional[ + Union[ + models.PreviewUpdateRecalculateBalances, + models.PreviewUpdateRecalculateBalancesTypedDict, + ] + ] = None, retries: OptionalNullable[utils.RetryConfig] = UNSET, server_url: Optional[str] = None, timeout_ms: Optional[int] = None, @@ -1869,6 +1905,7 @@ async def preview_update_async( :param subscription_id: A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. :param cancel_action: Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. :param no_billing_changes: If true, the subscription is updated internally without applying billing changes in Stripe. + :param recalculate_balances: Controls whether balances should be recalculated during the subscription update. :param retries: Override the default retry configuration for this method :param server_url: Override the default server URL for this method :param timeout_ms: Override the default request timeout configuration for this method in milliseconds @@ -1904,6 +1941,9 @@ async def preview_update_async( subscription_id=subscription_id, cancel_action=cancel_action, no_billing_changes=no_billing_changes, + recalculate_balances=utils.get_pydantic_model( + recalculate_balances, Optional[models.PreviewUpdateRecalculateBalances] + ), ) req = self._build_request_async( diff --git a/others/python-sdk/src/autumn_sdk/errors/__init__.py b/others/python-sdk/src/autumn_sdk/errors/__init__.py index 3c83f2947..712bfe798 100644 --- a/others/python-sdk/src/autumn_sdk/errors/__init__.py +++ b/others/python-sdk/src/autumn_sdk/errors/__init__.py @@ -1,9 +1,10 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" from .autumnerror import AutumnError -from typing import Any, TYPE_CHECKING - -from autumn_sdk.utils.dynamic_imports import lazy_getattr, lazy_dir +from typing import TYPE_CHECKING +from importlib import import_module +import builtins +import sys if TYPE_CHECKING: from .autumndefaulterror import AutumnDefaultError @@ -24,11 +25,39 @@ } -def __getattr__(attr_name: str) -> Any: - return lazy_getattr( - attr_name, package=__package__, dynamic_imports=_dynamic_imports - ) +def dynamic_import(modname, retries=3): + for attempt in range(retries): + try: + return import_module(modname, __package__) + except KeyError: + # Clear any half-initialized module and retry + sys.modules.pop(modname, None) + if attempt == retries - 1: + break + raise KeyError(f"Failed to import module '{modname}' after {retries} attempts") + + +def __getattr__(attr_name: str) -> object: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__} " + ) + + try: + module = dynamic_import(module_name) + result = getattr(module, attr_name) + return result + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e def __dir__(): - return lazy_dir(dynamic_imports=_dynamic_imports) + lazy_attrs = builtins.list(_dynamic_imports.keys()) + return builtins.sorted(lazy_attrs) diff --git a/others/python-sdk/src/autumn_sdk/models/__init__.py b/others/python-sdk/src/autumn_sdk/models/__init__.py index 0127c6be3..b397b6b41 100644 --- a/others/python-sdk/src/autumn_sdk/models/__init__.py +++ b/others/python-sdk/src/autumn_sdk/models/__init__.py @@ -1,8 +1,9 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" -from typing import Any, TYPE_CHECKING - -from autumn_sdk.utils.dynamic_imports import lazy_getattr, lazy_dir +from typing import TYPE_CHECKING +from importlib import import_module +import builtins +import sys if TYPE_CHECKING: from .aggregateeventsop import ( @@ -135,6 +136,8 @@ BillingUpdateProration, BillingUpdateProrationBehavior, BillingUpdateProrationTypedDict, + BillingUpdateRecalculateBalances, + BillingUpdateRecalculateBalancesTypedDict, BillingUpdateRedirectMode, BillingUpdateRequiredAction, BillingUpdateRequiredActionTypedDict, @@ -244,7 +247,13 @@ CreateEntityStatus, CreateEntitySubscription, CreateEntitySubscriptionTypedDict, + CreateEntityThresholdTypeRequestBody, + CreateEntityThresholdTypeResponse, CreateEntityType, + CreateEntityUsageAlertRequestBody, + CreateEntityUsageAlertRequestBodyTypedDict, + CreateEntityUsageAlertResponse, + CreateEntityUsageAlertResponseTypedDict, ) from .createfeatureop import ( CreateFeatureCreditSchemaRequest, @@ -363,7 +372,10 @@ CustomerSpendLimit, CustomerSpendLimitTypedDict, CustomerStatus, + CustomerThresholdType, CustomerTypedDict, + CustomerUsageAlert, + CustomerUsageAlertTypedDict, Discount, DiscountTypedDict, Entity, @@ -398,7 +410,10 @@ CustomerDataPurchaseLimitTypedDict, CustomerDataSpendLimit, CustomerDataSpendLimitTypedDict, + CustomerDataThresholdType, CustomerDataTypedDict, + CustomerDataUsageAlert, + CustomerDataUsageAlertTypedDict, ) from .deletebalanceop import ( DeleteBalanceGlobals, @@ -477,7 +492,10 @@ GetEntityStatus, GetEntitySubscription, GetEntitySubscriptionTypedDict, + GetEntityThresholdType, GetEntityType, + GetEntityUsageAlert, + GetEntityUsageAlertTypedDict, ) from .getfeatureop import ( GetFeatureCreditSchema, @@ -506,6 +524,9 @@ GetOrCreateCustomerPurchaseLimitTypedDict, GetOrCreateCustomerSpendLimit, GetOrCreateCustomerSpendLimitTypedDict, + GetOrCreateCustomerThresholdType, + GetOrCreateCustomerUsageAlert, + GetOrCreateCustomerUsageAlertTypedDict, ) from .getplanop import ( GetPlanAttachAction, @@ -584,7 +605,10 @@ ListCustomersStatus, ListCustomersSubscription, ListCustomersSubscriptionTypedDict, + ListCustomersThresholdType, ListCustomersType, + ListCustomersUsageAlert, + ListCustomersUsageAlertTypedDict, SubscriptionStatus, ) from .listeventsop import ( @@ -714,11 +738,14 @@ MultiAttachRolloverTypedDict, MultiAttachSpendLimit, MultiAttachSpendLimitTypedDict, + MultiAttachThresholdType, MultiAttachTier, MultiAttachTierBehavior, MultiAttachTierTypedDict, MultiAttachTo, MultiAttachToTypedDict, + MultiAttachUsageAlert, + MultiAttachUsageAlertTypedDict, ) from .opencustomerportalop import ( OpenCustomerPortalGlobals, @@ -913,11 +940,14 @@ PreviewMultiAttachRolloverTypedDict, PreviewMultiAttachSpendLimit, PreviewMultiAttachSpendLimitTypedDict, + PreviewMultiAttachThresholdType, PreviewMultiAttachTier, PreviewMultiAttachTierBehavior, PreviewMultiAttachTierTypedDict, PreviewMultiAttachTo, PreviewMultiAttachToTypedDict, + PreviewMultiAttachUsageAlert, + PreviewMultiAttachUsageAlertTypedDict, PreviewMultiAttachUsageLineItem, PreviewMultiAttachUsageLineItemPeriod, PreviewMultiAttachUsageLineItemPeriodTypedDict, @@ -976,6 +1006,8 @@ PreviewUpdateProration, PreviewUpdateProrationBehavior, PreviewUpdateProrationTypedDict, + PreviewUpdateRecalculateBalances, + PreviewUpdateRecalculateBalancesTypedDict, PreviewUpdateRedirectMode, PreviewUpdateReset, PreviewUpdateResetInterval, @@ -1109,7 +1141,13 @@ UpdateCustomerStatus, UpdateCustomerSubscription, UpdateCustomerSubscriptionTypedDict, + UpdateCustomerThresholdTypeRequestBody, + UpdateCustomerThresholdTypeResponse, UpdateCustomerType, + UpdateCustomerUsageAlertRequestBody, + UpdateCustomerUsageAlertRequestBodyTypedDict, + UpdateCustomerUsageAlertResponse, + UpdateCustomerUsageAlertResponseTypedDict, ) from .updateentityop import ( UpdateEntityBillingControlsRequest, @@ -1142,7 +1180,13 @@ UpdateEntityStatus, UpdateEntitySubscription, UpdateEntitySubscriptionTypedDict, + UpdateEntityThresholdTypeRequestBody, + UpdateEntityThresholdTypeResponse, UpdateEntityType, + UpdateEntityUsageAlertRequestBody, + UpdateEntityUsageAlertRequestBodyTypedDict, + UpdateEntityUsageAlertResponse, + UpdateEntityUsageAlertResponseTypedDict, ) from .updatefeatureop import ( UpdateFeatureCreditSchemaRequest, @@ -1232,7 +1276,6 @@ UpdatePlanToTypedDict, UpdatePlanType, ) - from . import internal __all__ = [ "Action", @@ -1350,6 +1393,8 @@ "BillingUpdateProration", "BillingUpdateProrationBehavior", "BillingUpdateProrationTypedDict", + "BillingUpdateRecalculateBalances", + "BillingUpdateRecalculateBalancesTypedDict", "BillingUpdateRedirectMode", "BillingUpdateRequiredAction", "BillingUpdateRequiredActionTypedDict", @@ -1434,7 +1479,13 @@ "CreateEntityStatus", "CreateEntitySubscription", "CreateEntitySubscriptionTypedDict", + "CreateEntityThresholdTypeRequestBody", + "CreateEntityThresholdTypeResponse", "CreateEntityType", + "CreateEntityUsageAlertRequestBody", + "CreateEntityUsageAlertRequestBodyTypedDict", + "CreateEntityUsageAlertResponse", + "CreateEntityUsageAlertResponseTypedDict", "CreateFeatureCreditSchemaRequest", "CreateFeatureCreditSchemaRequestTypedDict", "CreateFeatureCreditSchemaResponse", @@ -1540,7 +1591,10 @@ "CustomerDataPurchaseLimitTypedDict", "CustomerDataSpendLimit", "CustomerDataSpendLimitTypedDict", + "CustomerDataThresholdType", "CustomerDataTypedDict", + "CustomerDataUsageAlert", + "CustomerDataUsageAlertTypedDict", "CustomerDisplay", "CustomerDisplayTypedDict", "CustomerDurationType", @@ -1556,7 +1610,10 @@ "CustomerSpendLimit", "CustomerSpendLimitTypedDict", "CustomerStatus", + "CustomerThresholdType", "CustomerTypedDict", + "CustomerUsageAlert", + "CustomerUsageAlertTypedDict", "DeleteBalanceGlobals", "DeleteBalanceGlobalsTypedDict", "DeleteBalanceInterval", @@ -1643,7 +1700,10 @@ "GetEntityStatus", "GetEntitySubscription", "GetEntitySubscriptionTypedDict", + "GetEntityThresholdType", "GetEntityType", + "GetEntityUsageAlert", + "GetEntityUsageAlertTypedDict", "GetFeatureCreditSchema", "GetFeatureCreditSchemaTypedDict", "GetFeatureDisplay", @@ -1668,6 +1728,9 @@ "GetOrCreateCustomerPurchaseLimitTypedDict", "GetOrCreateCustomerSpendLimit", "GetOrCreateCustomerSpendLimitTypedDict", + "GetOrCreateCustomerThresholdType", + "GetOrCreateCustomerUsageAlert", + "GetOrCreateCustomerUsageAlertTypedDict", "GetPlanAttachAction", "GetPlanBillingMethod", "GetPlanCreditSchema", @@ -1751,7 +1814,10 @@ "ListCustomersStatus", "ListCustomersSubscription", "ListCustomersSubscriptionTypedDict", + "ListCustomersThresholdType", "ListCustomersType", + "ListCustomersUsageAlert", + "ListCustomersUsageAlertTypedDict", "ListEventsCustomRange", "ListEventsCustomRangeTypedDict", "ListEventsFeatureID", @@ -1870,11 +1936,14 @@ "MultiAttachRolloverTypedDict", "MultiAttachSpendLimit", "MultiAttachSpendLimitTypedDict", + "MultiAttachThresholdType", "MultiAttachTier", "MultiAttachTierBehavior", "MultiAttachTierTypedDict", "MultiAttachTo", "MultiAttachToTypedDict", + "MultiAttachUsageAlert", + "MultiAttachUsageAlertTypedDict", "OpenCustomerPortalGlobals", "OpenCustomerPortalGlobalsTypedDict", "OpenCustomerPortalParams", @@ -2054,11 +2123,14 @@ "PreviewMultiAttachRolloverTypedDict", "PreviewMultiAttachSpendLimit", "PreviewMultiAttachSpendLimitTypedDict", + "PreviewMultiAttachThresholdType", "PreviewMultiAttachTier", "PreviewMultiAttachTierBehavior", "PreviewMultiAttachTierTypedDict", "PreviewMultiAttachTo", "PreviewMultiAttachToTypedDict", + "PreviewMultiAttachUsageAlert", + "PreviewMultiAttachUsageAlertTypedDict", "PreviewMultiAttachUsageLineItem", "PreviewMultiAttachUsageLineItemPeriod", "PreviewMultiAttachUsageLineItemPeriodTypedDict", @@ -2115,6 +2187,8 @@ "PreviewUpdateProration", "PreviewUpdateProrationBehavior", "PreviewUpdateProrationTypedDict", + "PreviewUpdateRecalculateBalances", + "PreviewUpdateRecalculateBalancesTypedDict", "PreviewUpdateRedirectMode", "PreviewUpdateReset", "PreviewUpdateResetInterval", @@ -2264,7 +2338,13 @@ "UpdateCustomerStatus", "UpdateCustomerSubscription", "UpdateCustomerSubscriptionTypedDict", + "UpdateCustomerThresholdTypeRequestBody", + "UpdateCustomerThresholdTypeResponse", "UpdateCustomerType", + "UpdateCustomerUsageAlertRequestBody", + "UpdateCustomerUsageAlertRequestBodyTypedDict", + "UpdateCustomerUsageAlertResponse", + "UpdateCustomerUsageAlertResponseTypedDict", "UpdateEntityBillingControlsRequest", "UpdateEntityBillingControlsRequestTypedDict", "UpdateEntityBillingControlsResponse", @@ -2295,7 +2375,13 @@ "UpdateEntityStatus", "UpdateEntitySubscription", "UpdateEntitySubscriptionTypedDict", + "UpdateEntityThresholdTypeRequestBody", + "UpdateEntityThresholdTypeResponse", "UpdateEntityType", + "UpdateEntityUsageAlertRequestBody", + "UpdateEntityUsageAlertRequestBodyTypedDict", + "UpdateEntityUsageAlertResponse", + "UpdateEntityUsageAlertResponseTypedDict", "UpdateFeatureCreditSchemaRequest", "UpdateFeatureCreditSchemaRequestTypedDict", "UpdateFeatureCreditSchemaResponse", @@ -2509,6 +2595,8 @@ "BillingUpdateProration": ".billingupdateop", "BillingUpdateProrationBehavior": ".billingupdateop", "BillingUpdateProrationTypedDict": ".billingupdateop", + "BillingUpdateRecalculateBalances": ".billingupdateop", + "BillingUpdateRecalculateBalancesTypedDict": ".billingupdateop", "BillingUpdateRedirectMode": ".billingupdateop", "BillingUpdateRequiredAction": ".billingupdateop", "BillingUpdateRequiredActionTypedDict": ".billingupdateop", @@ -2612,7 +2700,13 @@ "CreateEntityStatus": ".createentityop", "CreateEntitySubscription": ".createentityop", "CreateEntitySubscriptionTypedDict": ".createentityop", + "CreateEntityThresholdTypeRequestBody": ".createentityop", + "CreateEntityThresholdTypeResponse": ".createentityop", "CreateEntityType": ".createentityop", + "CreateEntityUsageAlertRequestBody": ".createentityop", + "CreateEntityUsageAlertRequestBodyTypedDict": ".createentityop", + "CreateEntityUsageAlertResponse": ".createentityop", + "CreateEntityUsageAlertResponseTypedDict": ".createentityop", "CreateFeatureCreditSchemaRequest": ".createfeatureop", "CreateFeatureCreditSchemaRequestTypedDict": ".createfeatureop", "CreateFeatureCreditSchemaResponse": ".createfeatureop", @@ -2723,7 +2817,10 @@ "CustomerSpendLimit": ".customer", "CustomerSpendLimitTypedDict": ".customer", "CustomerStatus": ".customer", + "CustomerThresholdType": ".customer", "CustomerTypedDict": ".customer", + "CustomerUsageAlert": ".customer", + "CustomerUsageAlertTypedDict": ".customer", "Discount": ".customer", "DiscountTypedDict": ".customer", "Entity": ".customer", @@ -2756,7 +2853,10 @@ "CustomerDataPurchaseLimitTypedDict": ".customerdata", "CustomerDataSpendLimit": ".customerdata", "CustomerDataSpendLimitTypedDict": ".customerdata", + "CustomerDataThresholdType": ".customerdata", "CustomerDataTypedDict": ".customerdata", + "CustomerDataUsageAlert": ".customerdata", + "CustomerDataUsageAlertTypedDict": ".customerdata", "DeleteBalanceGlobals": ".deletebalanceop", "DeleteBalanceGlobalsTypedDict": ".deletebalanceop", "DeleteBalanceInterval": ".deletebalanceop", @@ -2821,7 +2921,10 @@ "GetEntityStatus": ".getentityop", "GetEntitySubscription": ".getentityop", "GetEntitySubscriptionTypedDict": ".getentityop", + "GetEntityThresholdType": ".getentityop", "GetEntityType": ".getentityop", + "GetEntityUsageAlert": ".getentityop", + "GetEntityUsageAlertTypedDict": ".getentityop", "GetFeatureCreditSchema": ".getfeatureop", "GetFeatureCreditSchemaTypedDict": ".getfeatureop", "GetFeatureDisplay": ".getfeatureop", @@ -2846,6 +2949,9 @@ "GetOrCreateCustomerPurchaseLimitTypedDict": ".getorcreatecustomerop", "GetOrCreateCustomerSpendLimit": ".getorcreatecustomerop", "GetOrCreateCustomerSpendLimitTypedDict": ".getorcreatecustomerop", + "GetOrCreateCustomerThresholdType": ".getorcreatecustomerop", + "GetOrCreateCustomerUsageAlert": ".getorcreatecustomerop", + "GetOrCreateCustomerUsageAlertTypedDict": ".getorcreatecustomerop", "GetPlanAttachAction": ".getplanop", "GetPlanBillingMethod": ".getplanop", "GetPlanCreditSchema": ".getplanop", @@ -2920,7 +3026,10 @@ "ListCustomersStatus": ".listcustomersop", "ListCustomersSubscription": ".listcustomersop", "ListCustomersSubscriptionTypedDict": ".listcustomersop", + "ListCustomersThresholdType": ".listcustomersop", "ListCustomersType": ".listcustomersop", + "ListCustomersUsageAlert": ".listcustomersop", + "ListCustomersUsageAlertTypedDict": ".listcustomersop", "SubscriptionStatus": ".listcustomersop", "EventsListParams": ".listeventsop", "EventsListParamsTypedDict": ".listeventsop", @@ -3042,11 +3151,14 @@ "MultiAttachRolloverTypedDict": ".multiattachop", "MultiAttachSpendLimit": ".multiattachop", "MultiAttachSpendLimitTypedDict": ".multiattachop", + "MultiAttachThresholdType": ".multiattachop", "MultiAttachTier": ".multiattachop", "MultiAttachTierBehavior": ".multiattachop", "MultiAttachTierTypedDict": ".multiattachop", "MultiAttachTo": ".multiattachop", "MultiAttachToTypedDict": ".multiattachop", + "MultiAttachUsageAlert": ".multiattachop", + "MultiAttachUsageAlertTypedDict": ".multiattachop", "OpenCustomerPortalGlobals": ".opencustomerportalop", "OpenCustomerPortalGlobalsTypedDict": ".opencustomerportalop", "OpenCustomerPortalParams": ".opencustomerportalop", @@ -3233,11 +3345,14 @@ "PreviewMultiAttachRolloverTypedDict": ".previewmultiattachop", "PreviewMultiAttachSpendLimit": ".previewmultiattachop", "PreviewMultiAttachSpendLimitTypedDict": ".previewmultiattachop", + "PreviewMultiAttachThresholdType": ".previewmultiattachop", "PreviewMultiAttachTier": ".previewmultiattachop", "PreviewMultiAttachTierBehavior": ".previewmultiattachop", "PreviewMultiAttachTierTypedDict": ".previewmultiattachop", "PreviewMultiAttachTo": ".previewmultiattachop", "PreviewMultiAttachToTypedDict": ".previewmultiattachop", + "PreviewMultiAttachUsageAlert": ".previewmultiattachop", + "PreviewMultiAttachUsageAlertTypedDict": ".previewmultiattachop", "PreviewMultiAttachUsageLineItem": ".previewmultiattachop", "PreviewMultiAttachUsageLineItemPeriod": ".previewmultiattachop", "PreviewMultiAttachUsageLineItemPeriodTypedDict": ".previewmultiattachop", @@ -3294,6 +3409,8 @@ "PreviewUpdateProration": ".previewupdateop", "PreviewUpdateProrationBehavior": ".previewupdateop", "PreviewUpdateProrationTypedDict": ".previewupdateop", + "PreviewUpdateRecalculateBalances": ".previewupdateop", + "PreviewUpdateRecalculateBalancesTypedDict": ".previewupdateop", "PreviewUpdateRedirectMode": ".previewupdateop", "PreviewUpdateReset": ".previewupdateop", "PreviewUpdateResetInterval": ".previewupdateop", @@ -3418,7 +3535,13 @@ "UpdateCustomerStatus": ".updatecustomerop", "UpdateCustomerSubscription": ".updatecustomerop", "UpdateCustomerSubscriptionTypedDict": ".updatecustomerop", + "UpdateCustomerThresholdTypeRequestBody": ".updatecustomerop", + "UpdateCustomerThresholdTypeResponse": ".updatecustomerop", "UpdateCustomerType": ".updatecustomerop", + "UpdateCustomerUsageAlertRequestBody": ".updatecustomerop", + "UpdateCustomerUsageAlertRequestBodyTypedDict": ".updatecustomerop", + "UpdateCustomerUsageAlertResponse": ".updatecustomerop", + "UpdateCustomerUsageAlertResponseTypedDict": ".updatecustomerop", "UpdateEntityBillingControlsRequest": ".updateentityop", "UpdateEntityBillingControlsRequestTypedDict": ".updateentityop", "UpdateEntityBillingControlsResponse": ".updateentityop", @@ -3449,7 +3572,13 @@ "UpdateEntityStatus": ".updateentityop", "UpdateEntitySubscription": ".updateentityop", "UpdateEntitySubscriptionTypedDict": ".updateentityop", + "UpdateEntityThresholdTypeRequestBody": ".updateentityop", + "UpdateEntityThresholdTypeResponse": ".updateentityop", "UpdateEntityType": ".updateentityop", + "UpdateEntityUsageAlertRequestBody": ".updateentityop", + "UpdateEntityUsageAlertRequestBodyTypedDict": ".updateentityop", + "UpdateEntityUsageAlertResponse": ".updateentityop", + "UpdateEntityUsageAlertResponseTypedDict": ".updateentityop", "UpdateFeatureCreditSchemaRequest": ".updatefeatureop", "UpdateFeatureCreditSchemaRequestTypedDict": ".updatefeatureop", "UpdateFeatureCreditSchemaResponse": ".updatefeatureop", @@ -3536,17 +3665,40 @@ "UpdatePlanType": ".updateplanop", } -_sub_packages = ["internal"] +def dynamic_import(modname, retries=3): + for attempt in range(retries): + try: + return import_module(modname, __package__) + except KeyError: + # Clear any half-initialized module and retry + sys.modules.pop(modname, None) + if attempt == retries - 1: + break + raise KeyError(f"Failed to import module '{modname}' after {retries} attempts") -def __getattr__(attr_name: str) -> Any: - return lazy_getattr( - attr_name, - package=__package__, - dynamic_imports=_dynamic_imports, - sub_packages=_sub_packages, - ) + +def __getattr__(attr_name: str) -> object: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__} " + ) + + try: + module = dynamic_import(module_name) + result = getattr(module, attr_name) + return result + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e def __dir__(): - return lazy_dir(dynamic_imports=_dynamic_imports, sub_packages=_sub_packages) + lazy_attrs = builtins.list(_dynamic_imports.keys()) + return builtins.sorted(lazy_attrs) diff --git a/others/python-sdk/src/autumn_sdk/models/aggregateeventsop.py b/others/python-sdk/src/autumn_sdk/models/aggregateeventsop.py index b62e4c617..c6a100bbd 100644 --- a/others/python-sdk/src/autumn_sdk/models/aggregateeventsop.py +++ b/others/python-sdk/src/autumn_sdk/models/aggregateeventsop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -140,7 +140,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -176,7 +176,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/attachop.py b/others/python-sdk/src/autumn_sdk/models/attachop.py index 434a571ef..12b3c3c9c 100644 --- a/others/python-sdk/src/autumn_sdk/models/attachop.py +++ b/others/python-sdk/src/autumn_sdk/models/attachop.py @@ -35,7 +35,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -75,7 +75,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -126,7 +126,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -175,7 +175,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -211,7 +211,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -307,7 +307,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -392,7 +392,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -454,7 +454,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -502,7 +502,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -543,7 +543,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -591,7 +591,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -641,7 +641,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -698,7 +698,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -733,7 +733,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -869,7 +869,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -918,7 +918,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: m[k] = val @@ -998,7 +998,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/balance.py b/others/python-sdk/src/autumn_sdk/models/balance.py index f08b03da9..9e4d3d82c 100644 --- a/others/python-sdk/src/autumn_sdk/models/balance.py +++ b/others/python-sdk/src/autumn_sdk/models/balance.py @@ -67,7 +67,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -140,7 +140,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -201,7 +201,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -281,7 +281,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -361,7 +361,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -461,7 +461,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/billingupdateop.py b/others/python-sdk/src/autumn_sdk/models/billingupdateop.py index 944115475..52804986f 100644 --- a/others/python-sdk/src/autumn_sdk/models/billingupdateop.py +++ b/others/python-sdk/src/autumn_sdk/models/billingupdateop.py @@ -35,7 +35,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -75,7 +75,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -126,7 +126,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -175,7 +175,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -211,7 +211,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -307,7 +307,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -392,7 +392,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -454,7 +454,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -502,7 +502,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -543,7 +543,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -591,7 +591,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -623,6 +623,20 @@ def serialize_model(self, handler): r"""Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation.""" +class BillingUpdateRecalculateBalancesTypedDict(TypedDict): + r"""Controls whether balances should be recalculated during the subscription update.""" + + enabled: bool + r"""If true, recalculates balances during the subscription update. Only applicable when updating feature quantities.""" + + +class BillingUpdateRecalculateBalances(BaseModel): + r"""Controls whether balances should be recalculated during the subscription update.""" + + enabled: bool + r"""If true, recalculates balances during the subscription update. Only applicable when updating feature quantities.""" + + class UpdateSubscriptionParamsTypedDict(TypedDict): customer_id: str r"""The ID of the customer to attach the plan to.""" @@ -648,6 +662,8 @@ class UpdateSubscriptionParamsTypedDict(TypedDict): r"""Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation.""" no_billing_changes: NotRequired[bool] r"""If true, the subscription is updated internally without applying billing changes in Stripe.""" + recalculate_balances: NotRequired[BillingUpdateRecalculateBalancesTypedDict] + r"""Controls whether balances should be recalculated during the subscription update.""" class UpdateSubscriptionParams(BaseModel): @@ -687,6 +703,9 @@ class UpdateSubscriptionParams(BaseModel): no_billing_changes: Optional[bool] = None r"""If true, the subscription is updated internally without applying billing changes in Stripe.""" + recalculate_balances: Optional[BillingUpdateRecalculateBalances] = None + r"""Controls whether balances should be recalculated during the subscription update.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): optional_fields = set( @@ -702,6 +721,7 @@ def serialize_model(self, handler): "subscription_id", "cancel_action", "no_billing_changes", + "recalculate_balances", ] ) serialized = handler(self) @@ -709,7 +729,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -758,7 +778,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: m[k] = val @@ -838,7 +858,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/checkop.py b/others/python-sdk/src/autumn_sdk/models/checkop.py index 0d0e2e977..81008db9b 100644 --- a/others/python-sdk/src/autumn_sdk/models/checkop.py +++ b/others/python-sdk/src/autumn_sdk/models/checkop.py @@ -37,7 +37,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -80,7 +80,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -150,7 +150,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -212,7 +212,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -285,7 +285,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -332,7 +332,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -448,7 +448,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -496,7 +496,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -558,7 +558,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -717,7 +717,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -783,7 +783,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -855,7 +855,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -953,7 +953,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1061,7 +1061,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/createbalanceop.py b/others/python-sdk/src/autumn_sdk/models/createbalanceop.py index 5f8b89b4b..674607646 100644 --- a/others/python-sdk/src/autumn_sdk/models/createbalanceop.py +++ b/others/python-sdk/src/autumn_sdk/models/createbalanceop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -77,7 +77,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -147,7 +147,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/createentityop.py b/others/python-sdk/src/autumn_sdk/models/createentityop.py index 006b843c4..29bc6efc3 100644 --- a/others/python-sdk/src/autumn_sdk/models/createentityop.py +++ b/others/python-sdk/src/autumn_sdk/models/createentityop.py @@ -38,7 +38,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -74,7 +74,60 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +CreateEntityThresholdTypeRequestBody = Literal[ + "usage", + "usage_percentage", +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class CreateEntityUsageAlertRequestBodyTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: CreateEntityThresholdTypeRequestBody + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class CreateEntityUsageAlertRequestBody(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: CreateEntityThresholdTypeRequestBody + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -88,6 +141,8 @@ class CreateEntityBillingControlsRequestTypedDict(TypedDict): spend_limits: NotRequired[List[CreateEntitySpendLimitRequestTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[CreateEntityUsageAlertRequestBodyTypedDict]] + r"""List of usage alert configurations per feature.""" class CreateEntityBillingControlsRequest(BaseModel): @@ -96,15 +151,18 @@ class CreateEntityBillingControlsRequest(BaseModel): spend_limits: Optional[List[CreateEntitySpendLimitRequest]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[CreateEntityUsageAlertRequestBody]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["spend_limits"]) + optional_fields = set(["spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -156,7 +214,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -282,7 +340,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -335,7 +393,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -405,7 +463,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -478,7 +536,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -525,7 +583,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -569,7 +627,63 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +CreateEntityThresholdTypeResponse = Union[ + Literal[ + "usage", + "usage_percentage", + ], + UnrecognizedStr, +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class CreateEntityUsageAlertResponseTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: CreateEntityThresholdTypeResponse + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class CreateEntityUsageAlertResponse(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: CreateEntityThresholdTypeResponse + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -583,6 +697,8 @@ class CreateEntityBillingControlsResponseTypedDict(TypedDict): spend_limits: NotRequired[List[CreateEntitySpendLimitResponseTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[CreateEntityUsageAlertResponseTypedDict]] + r"""List of usage alert configurations per feature.""" class CreateEntityBillingControlsResponse(BaseModel): @@ -591,15 +707,18 @@ class CreateEntityBillingControlsResponse(BaseModel): spend_limits: Optional[List[CreateEntitySpendLimitResponse]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[CreateEntityUsageAlertResponse]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["spend_limits"]) + optional_fields = set(["spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -656,7 +775,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -744,7 +863,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/createfeatureop.py b/others/python-sdk/src/autumn_sdk/models/createfeatureop.py index 35a43e4dd..b6be52d60 100644 --- a/others/python-sdk/src/autumn_sdk/models/createfeatureop.py +++ b/others/python-sdk/src/autumn_sdk/models/createfeatureop.py @@ -35,7 +35,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -123,7 +123,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -185,7 +185,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -258,7 +258,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/createplanop.py b/others/python-sdk/src/autumn_sdk/models/createplanop.py index 68ffef898..3188aa943 100644 --- a/others/python-sdk/src/autumn_sdk/models/createplanop.py +++ b/others/python-sdk/src/autumn_sdk/models/createplanop.py @@ -35,7 +35,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -86,7 +86,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -135,7 +135,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -171,7 +171,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -267,7 +267,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -352,7 +352,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -414,7 +414,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -462,7 +462,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -539,7 +539,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -596,7 +596,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -637,7 +637,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -736,7 +736,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -792,7 +792,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -885,7 +885,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -928,7 +928,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -979,7 +979,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1049,7 +1049,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1171,7 +1171,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -1270,7 +1270,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/createreferralcodeop.py b/others/python-sdk/src/autumn_sdk/models/createreferralcodeop.py index cd3ad4890..60bb089e7 100644 --- a/others/python-sdk/src/autumn_sdk/models/createreferralcodeop.py +++ b/others/python-sdk/src/autumn_sdk/models/createreferralcodeop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/customer.py b/others/python-sdk/src/autumn_sdk/models/customer.py index e041b7cf1..52b3cda11 100644 --- a/others/python-sdk/src/autumn_sdk/models/customer.py +++ b/others/python-sdk/src/autumn_sdk/models/customer.py @@ -69,7 +69,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -115,7 +115,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -151,7 +151,63 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +CustomerThresholdType = Union[ + Literal[ + "usage", + "usage_percentage", + ], + UnrecognizedStr, +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class CustomerUsageAlertTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: CustomerThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class CustomerUsageAlert(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: CustomerThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -167,6 +223,8 @@ class CustomerBillingControlsTypedDict(TypedDict): r"""List of auto top-up configurations per feature.""" spend_limits: NotRequired[List[CustomerSpendLimitTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[CustomerUsageAlertTypedDict]] + r"""List of usage alert configurations per feature.""" class CustomerBillingControls(BaseModel): @@ -178,15 +236,18 @@ class CustomerBillingControls(BaseModel): spend_limits: Optional[List[CustomerSpendLimit]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[CustomerUsageAlert]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["auto_topups", "spend_limits"]) + optional_fields = set(["auto_topups", "spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -294,7 +355,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -347,7 +408,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -417,7 +478,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -490,7 +551,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -537,7 +598,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -602,7 +663,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -672,7 +733,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -711,7 +772,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -837,7 +898,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -886,7 +947,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1052,7 +1113,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/customerdata.py b/others/python-sdk/src/autumn_sdk/models/customerdata.py index 192060327..78cb71f16 100644 --- a/others/python-sdk/src/autumn_sdk/models/customerdata.py +++ b/others/python-sdk/src/autumn_sdk/models/customerdata.py @@ -53,7 +53,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -99,7 +99,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -135,7 +135,60 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +CustomerDataThresholdType = Literal[ + "usage", + "usage_percentage", +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class CustomerDataUsageAlertTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: CustomerDataThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class CustomerDataUsageAlert(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: CustomerDataThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -151,6 +204,8 @@ class CustomerDataBillingControlsTypedDict(TypedDict): r"""List of auto top-up configurations per feature.""" spend_limits: NotRequired[List[CustomerDataSpendLimitTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[CustomerDataUsageAlertTypedDict]] + r"""List of usage alert configurations per feature.""" class CustomerDataBillingControls(BaseModel): @@ -162,15 +217,18 @@ class CustomerDataBillingControls(BaseModel): spend_limits: Optional[List[CustomerDataSpendLimit]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[CustomerDataUsageAlert]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["auto_topups", "spend_limits"]) + optional_fields = set(["auto_topups", "spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -253,7 +311,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/deletebalanceop.py b/others/python-sdk/src/autumn_sdk/models/deletebalanceop.py index e93512ba6..8d2835818 100644 --- a/others/python-sdk/src/autumn_sdk/models/deletebalanceop.py +++ b/others/python-sdk/src/autumn_sdk/models/deletebalanceop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -88,7 +88,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/deletecustomerop.py b/others/python-sdk/src/autumn_sdk/models/deletecustomerop.py index f731ec176..360a0ca57 100644 --- a/others/python-sdk/src/autumn_sdk/models/deletecustomerop.py +++ b/others/python-sdk/src/autumn_sdk/models/deletecustomerop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -59,7 +59,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/deleteentityop.py b/others/python-sdk/src/autumn_sdk/models/deleteentityop.py index 994654d79..3aec2ed90 100644 --- a/others/python-sdk/src/autumn_sdk/models/deleteentityop.py +++ b/others/python-sdk/src/autumn_sdk/models/deleteentityop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -59,7 +59,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/deletefeatureop.py b/others/python-sdk/src/autumn_sdk/models/deletefeatureop.py index d16ff515b..1cb236ef4 100644 --- a/others/python-sdk/src/autumn_sdk/models/deletefeatureop.py +++ b/others/python-sdk/src/autumn_sdk/models/deletefeatureop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/deleteplanop.py b/others/python-sdk/src/autumn_sdk/models/deleteplanop.py index 839992760..8d60176d4 100644 --- a/others/python-sdk/src/autumn_sdk/models/deleteplanop.py +++ b/others/python-sdk/src/autumn_sdk/models/deleteplanop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -59,7 +59,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/finalizelockop.py b/others/python-sdk/src/autumn_sdk/models/finalizelockop.py index 5ca71e3f0..7fe4a8380 100644 --- a/others/python-sdk/src/autumn_sdk/models/finalizelockop.py +++ b/others/python-sdk/src/autumn_sdk/models/finalizelockop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -76,7 +76,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/getentityop.py b/others/python-sdk/src/autumn_sdk/models/getentityop.py index 956fe9aac..828874eab 100644 --- a/others/python-sdk/src/autumn_sdk/models/getentityop.py +++ b/others/python-sdk/src/autumn_sdk/models/getentityop.py @@ -37,7 +37,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -68,7 +68,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -186,7 +186,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -239,7 +239,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -309,7 +309,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -382,7 +382,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -429,7 +429,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -473,7 +473,63 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +GetEntityThresholdType = Union[ + Literal[ + "usage", + "usage_percentage", + ], + UnrecognizedStr, +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class GetEntityUsageAlertTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: GetEntityThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class GetEntityUsageAlert(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: GetEntityThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -487,6 +543,8 @@ class GetEntityBillingControlsTypedDict(TypedDict): spend_limits: NotRequired[List[GetEntitySpendLimitTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[GetEntityUsageAlertTypedDict]] + r"""List of usage alert configurations per feature.""" class GetEntityBillingControls(BaseModel): @@ -495,15 +553,18 @@ class GetEntityBillingControls(BaseModel): spend_limits: Optional[List[GetEntitySpendLimit]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[GetEntityUsageAlert]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["spend_limits"]) + optional_fields = set(["spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -560,7 +621,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -648,7 +709,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/getfeatureop.py b/others/python-sdk/src/autumn_sdk/models/getfeatureop.py index 63aec0714..6a0221630 100644 --- a/others/python-sdk/src/autumn_sdk/models/getfeatureop.py +++ b/others/python-sdk/src/autumn_sdk/models/getfeatureop.py @@ -35,7 +35,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -107,7 +107,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -180,7 +180,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/getorcreatecustomerop.py b/others/python-sdk/src/autumn_sdk/models/getorcreatecustomerop.py index a26722bad..c90706315 100644 --- a/others/python-sdk/src/autumn_sdk/models/getorcreatecustomerop.py +++ b/others/python-sdk/src/autumn_sdk/models/getorcreatecustomerop.py @@ -34,7 +34,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -83,7 +83,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -129,7 +129,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -165,7 +165,60 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +GetOrCreateCustomerThresholdType = Literal[ + "usage", + "usage_percentage", +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class GetOrCreateCustomerUsageAlertTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: GetOrCreateCustomerThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class GetOrCreateCustomerUsageAlert(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: GetOrCreateCustomerThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -181,6 +234,8 @@ class GetOrCreateCustomerBillingControlsTypedDict(TypedDict): r"""List of auto top-up configurations per feature.""" spend_limits: NotRequired[List[GetOrCreateCustomerSpendLimitTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[GetOrCreateCustomerUsageAlertTypedDict]] + r"""List of usage alert configurations per feature.""" class GetOrCreateCustomerBillingControls(BaseModel): @@ -192,15 +247,18 @@ class GetOrCreateCustomerBillingControls(BaseModel): spend_limits: Optional[List[GetOrCreateCustomerSpendLimit]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[GetOrCreateCustomerUsageAlert]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["auto_topups", "spend_limits"]) + optional_fields = set(["auto_topups", "spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -290,7 +348,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/getplanop.py b/others/python-sdk/src/autumn_sdk/models/getplanop.py index 03d634d43..1c7980ff7 100644 --- a/others/python-sdk/src/autumn_sdk/models/getplanop.py +++ b/others/python-sdk/src/autumn_sdk/models/getplanop.py @@ -35,7 +35,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -66,7 +66,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -115,7 +115,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -156,7 +156,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -255,7 +255,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -311,7 +311,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -404,7 +404,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -447,7 +447,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -498,7 +498,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -568,7 +568,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -690,7 +690,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -789,7 +789,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/internal/__init__.py b/others/python-sdk/src/autumn_sdk/models/internal/__init__.py index ceabb1b83..e7070a122 100644 --- a/others/python-sdk/src/autumn_sdk/models/internal/__init__.py +++ b/others/python-sdk/src/autumn_sdk/models/internal/__init__.py @@ -1,8 +1,9 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" -from typing import Any, TYPE_CHECKING - -from autumn_sdk.utils.dynamic_imports import lazy_getattr, lazy_dir +from typing import TYPE_CHECKING +from importlib import import_module +import builtins +import sys if TYPE_CHECKING: from .globals import Globals, GlobalsTypedDict @@ -15,11 +16,39 @@ } -def __getattr__(attr_name: str) -> Any: - return lazy_getattr( - attr_name, package=__package__, dynamic_imports=_dynamic_imports - ) +def dynamic_import(modname, retries=3): + for attempt in range(retries): + try: + return import_module(modname, __package__) + except KeyError: + # Clear any half-initialized module and retry + sys.modules.pop(modname, None) + if attempt == retries - 1: + break + raise KeyError(f"Failed to import module '{modname}' after {retries} attempts") + + +def __getattr__(attr_name: str) -> object: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__} " + ) + + try: + module = dynamic_import(module_name) + result = getattr(module, attr_name) + return result + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e def __dir__(): - return lazy_dir(dynamic_imports=_dynamic_imports) + lazy_attrs = builtins.list(_dynamic_imports.keys()) + return builtins.sorted(lazy_attrs) diff --git a/others/python-sdk/src/autumn_sdk/models/internal/globals.py b/others/python-sdk/src/autumn_sdk/models/internal/globals.py index 72107ef26..9b32efedd 100644 --- a/others/python-sdk/src/autumn_sdk/models/internal/globals.py +++ b/others/python-sdk/src/autumn_sdk/models/internal/globals.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/listcustomersop.py b/others/python-sdk/src/autumn_sdk/models/listcustomersop.py index 94dfc819f..701f0a5aa 100644 --- a/others/python-sdk/src/autumn_sdk/models/listcustomersop.py +++ b/others/python-sdk/src/autumn_sdk/models/listcustomersop.py @@ -37,7 +37,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -64,7 +64,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -119,7 +119,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -181,7 +181,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -227,7 +227,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -263,7 +263,63 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +ListCustomersThresholdType = Union[ + Literal[ + "usage", + "usage_percentage", + ], + UnrecognizedStr, +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class ListCustomersUsageAlertTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: ListCustomersThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class ListCustomersUsageAlert(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: ListCustomersThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -279,6 +335,8 @@ class ListCustomersBillingControlsTypedDict(TypedDict): r"""List of auto top-up configurations per feature.""" spend_limits: NotRequired[List[ListCustomersSpendLimitTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[ListCustomersUsageAlertTypedDict]] + r"""List of usage alert configurations per feature.""" class ListCustomersBillingControls(BaseModel): @@ -290,15 +348,18 @@ class ListCustomersBillingControls(BaseModel): spend_limits: Optional[List[ListCustomersSpendLimit]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[ListCustomersUsageAlert]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["auto_topups", "spend_limits"]) + optional_fields = set(["auto_topups", "spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -406,7 +467,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -459,7 +520,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -529,7 +590,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -602,7 +663,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -649,7 +710,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -747,7 +808,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: m[k] = val diff --git a/others/python-sdk/src/autumn_sdk/models/listeventsop.py b/others/python-sdk/src/autumn_sdk/models/listeventsop.py index 98b7f60ad..52845e969 100644 --- a/others/python-sdk/src/autumn_sdk/models/listeventsop.py +++ b/others/python-sdk/src/autumn_sdk/models/listeventsop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -73,7 +73,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -133,7 +133,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/listfeaturesop.py b/others/python-sdk/src/autumn_sdk/models/listfeaturesop.py index 710a3e367..b6731d7f7 100644 --- a/others/python-sdk/src/autumn_sdk/models/listfeaturesop.py +++ b/others/python-sdk/src/autumn_sdk/models/listfeaturesop.py @@ -35,7 +35,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -105,7 +105,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -174,7 +174,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/listplansop.py b/others/python-sdk/src/autumn_sdk/models/listplansop.py index bd090552f..55bab58d8 100644 --- a/others/python-sdk/src/autumn_sdk/models/listplansop.py +++ b/others/python-sdk/src/autumn_sdk/models/listplansop.py @@ -35,7 +35,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -71,7 +71,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -120,7 +120,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -161,7 +161,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -260,7 +260,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -316,7 +316,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -409,7 +409,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -452,7 +452,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -503,7 +503,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -573,7 +573,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -695,7 +695,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -794,7 +794,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/multiattachop.py b/others/python-sdk/src/autumn_sdk/models/multiattachop.py index bb5ed660b..2b48c43af 100644 --- a/others/python-sdk/src/autumn_sdk/models/multiattachop.py +++ b/others/python-sdk/src/autumn_sdk/models/multiattachop.py @@ -36,7 +36,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -87,7 +87,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -136,7 +136,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -172,7 +172,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -268,7 +268,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -353,7 +353,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -415,7 +415,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -451,7 +451,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -499,7 +499,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -547,7 +547,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -595,7 +595,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -635,7 +635,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -670,7 +670,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -714,7 +714,60 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +MultiAttachThresholdType = Literal[ + "usage", + "usage_percentage", +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class MultiAttachUsageAlertTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: MultiAttachThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class MultiAttachUsageAlert(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: MultiAttachThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -728,6 +781,8 @@ class MultiAttachBillingControlsTypedDict(TypedDict): spend_limits: NotRequired[List[MultiAttachSpendLimitTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[MultiAttachUsageAlertTypedDict]] + r"""List of usage alert configurations per feature.""" class MultiAttachBillingControls(BaseModel): @@ -736,15 +791,18 @@ class MultiAttachBillingControls(BaseModel): spend_limits: Optional[List[MultiAttachSpendLimit]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[MultiAttachUsageAlert]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["spend_limits"]) + optional_fields = set(["spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -780,7 +838,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -873,7 +931,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -930,7 +988,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: m[k] = val @@ -1010,7 +1068,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/opencustomerportalop.py b/others/python-sdk/src/autumn_sdk/models/opencustomerportalop.py index 8383509aa..bbf47f20c 100644 --- a/others/python-sdk/src/autumn_sdk/models/opencustomerportalop.py +++ b/others/python-sdk/src/autumn_sdk/models/opencustomerportalop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -64,7 +64,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/plan.py b/others/python-sdk/src/autumn_sdk/models/plan.py index 20844c9b6..56a42a2d2 100644 --- a/others/python-sdk/src/autumn_sdk/models/plan.py +++ b/others/python-sdk/src/autumn_sdk/models/plan.py @@ -54,7 +54,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -95,7 +95,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -194,7 +194,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -250,7 +250,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -343,7 +343,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -386,7 +386,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -437,7 +437,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -507,7 +507,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -629,7 +629,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -724,7 +724,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/previewattachop.py b/others/python-sdk/src/autumn_sdk/models/previewattachop.py index 60df178cf..dacc7ba98 100644 --- a/others/python-sdk/src/autumn_sdk/models/previewattachop.py +++ b/others/python-sdk/src/autumn_sdk/models/previewattachop.py @@ -35,7 +35,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -75,7 +75,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -126,7 +126,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -175,7 +175,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -211,7 +211,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -307,7 +307,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -392,7 +392,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -454,7 +454,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -502,7 +502,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -543,7 +543,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -591,7 +591,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -641,7 +641,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -698,7 +698,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -733,7 +733,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -869,7 +869,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -902,7 +902,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -988,7 +988,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1029,7 +1029,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -1115,7 +1115,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1184,7 +1184,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1257,6 +1257,10 @@ class PreviewAttachIncomingTypedDict(TypedDict): r"""The feature quantity selections associated with this plan change.""" effective_at: Nullable[float] r"""When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately.""" + canceled_at: Nullable[float] + r"""When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled.""" + expires_at: Nullable[float] + r"""When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire.""" plan: NotRequired[PlanTypedDict] @@ -1270,18 +1274,24 @@ class PreviewAttachIncoming(BaseModel): effective_at: Nullable[float] r"""When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately.""" + canceled_at: Nullable[float] + r"""When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled.""" + + expires_at: Nullable[float] + r"""When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire.""" + plan: Optional[Plan] = None @model_serializer(mode="wrap") def serialize_model(self, handler): optional_fields = set(["plan"]) - nullable_fields = set(["effective_at"]) + nullable_fields = set(["effective_at", "canceled_at", "expires_at"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1320,6 +1330,10 @@ class PreviewAttachOutgoingTypedDict(TypedDict): r"""The feature quantity selections associated with this plan change.""" effective_at: Nullable[float] r"""When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately.""" + canceled_at: Nullable[float] + r"""When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled.""" + expires_at: Nullable[float] + r"""When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire.""" plan: NotRequired[PlanTypedDict] @@ -1333,18 +1347,24 @@ class PreviewAttachOutgoing(BaseModel): effective_at: Nullable[float] r"""When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately.""" + canceled_at: Nullable[float] + r"""When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled.""" + + expires_at: Nullable[float] + r"""When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire.""" + plan: Optional[Plan] = None @model_serializer(mode="wrap") def serialize_model(self, handler): optional_fields = set(["plan"]) - nullable_fields = set(["effective_at"]) + nullable_fields = set(["effective_at", "canceled_at", "expires_at"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1422,7 +1442,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/previewmultiattachop.py b/others/python-sdk/src/autumn_sdk/models/previewmultiattachop.py index 187099042..06df124f5 100644 --- a/others/python-sdk/src/autumn_sdk/models/previewmultiattachop.py +++ b/others/python-sdk/src/autumn_sdk/models/previewmultiattachop.py @@ -36,7 +36,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -87,7 +87,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -136,7 +136,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -174,7 +174,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -270,7 +270,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -355,7 +355,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -417,7 +417,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -453,7 +453,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -501,7 +501,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -551,7 +551,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -599,7 +599,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -639,7 +639,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -674,7 +674,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -718,7 +718,60 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +PreviewMultiAttachThresholdType = Literal[ + "usage", + "usage_percentage", +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class PreviewMultiAttachUsageAlertTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: PreviewMultiAttachThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class PreviewMultiAttachUsageAlert(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: PreviewMultiAttachThresholdType + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -732,6 +785,8 @@ class PreviewMultiAttachBillingControlsTypedDict(TypedDict): spend_limits: NotRequired[List[PreviewMultiAttachSpendLimitTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[PreviewMultiAttachUsageAlertTypedDict]] + r"""List of usage alert configurations per feature.""" class PreviewMultiAttachBillingControls(BaseModel): @@ -740,15 +795,18 @@ class PreviewMultiAttachBillingControls(BaseModel): spend_limits: Optional[List[PreviewMultiAttachSpendLimit]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[PreviewMultiAttachUsageAlert]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["spend_limits"]) + optional_fields = set(["spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -784,7 +842,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -877,7 +935,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -918,7 +976,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -1004,7 +1062,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1045,7 +1103,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -1131,7 +1189,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1200,7 +1258,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1273,6 +1331,10 @@ class PreviewMultiAttachIncomingTypedDict(TypedDict): r"""The feature quantity selections associated with this plan change.""" effective_at: Nullable[float] r"""When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately.""" + canceled_at: Nullable[float] + r"""When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled.""" + expires_at: Nullable[float] + r"""When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire.""" plan: NotRequired[PlanTypedDict] @@ -1286,18 +1348,24 @@ class PreviewMultiAttachIncoming(BaseModel): effective_at: Nullable[float] r"""When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately.""" + canceled_at: Nullable[float] + r"""When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled.""" + + expires_at: Nullable[float] + r"""When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire.""" + plan: Optional[Plan] = None @model_serializer(mode="wrap") def serialize_model(self, handler): optional_fields = set(["plan"]) - nullable_fields = set(["effective_at"]) + nullable_fields = set(["effective_at", "canceled_at", "expires_at"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1336,6 +1404,10 @@ class PreviewMultiAttachOutgoingTypedDict(TypedDict): r"""The feature quantity selections associated with this plan change.""" effective_at: Nullable[float] r"""When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately.""" + canceled_at: Nullable[float] + r"""When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled.""" + expires_at: Nullable[float] + r"""When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire.""" plan: NotRequired[PlanTypedDict] @@ -1349,18 +1421,24 @@ class PreviewMultiAttachOutgoing(BaseModel): effective_at: Nullable[float] r"""When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately.""" + canceled_at: Nullable[float] + r"""When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled.""" + + expires_at: Nullable[float] + r"""When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire.""" + plan: Optional[Plan] = None @model_serializer(mode="wrap") def serialize_model(self, handler): optional_fields = set(["plan"]) - nullable_fields = set(["effective_at"]) + nullable_fields = set(["effective_at", "canceled_at", "expires_at"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1438,7 +1516,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/previewupdateop.py b/others/python-sdk/src/autumn_sdk/models/previewupdateop.py index 2c257a59e..0602a85b6 100644 --- a/others/python-sdk/src/autumn_sdk/models/previewupdateop.py +++ b/others/python-sdk/src/autumn_sdk/models/previewupdateop.py @@ -36,7 +36,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -76,7 +76,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -127,7 +127,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -176,7 +176,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -212,7 +212,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -308,7 +308,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -393,7 +393,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -455,7 +455,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -503,7 +503,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -544,7 +544,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -592,7 +592,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -624,6 +624,20 @@ def serialize_model(self, handler): r"""Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation.""" +class PreviewUpdateRecalculateBalancesTypedDict(TypedDict): + r"""Controls whether balances should be recalculated during the subscription update.""" + + enabled: bool + r"""If true, recalculates balances during the subscription update. Only applicable when updating feature quantities.""" + + +class PreviewUpdateRecalculateBalances(BaseModel): + r"""Controls whether balances should be recalculated during the subscription update.""" + + enabled: bool + r"""If true, recalculates balances during the subscription update. Only applicable when updating feature quantities.""" + + class PreviewUpdateParamsTypedDict(TypedDict): customer_id: str r"""The ID of the customer to attach the plan to.""" @@ -649,6 +663,8 @@ class PreviewUpdateParamsTypedDict(TypedDict): r"""Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation.""" no_billing_changes: NotRequired[bool] r"""If true, the subscription is updated internally without applying billing changes in Stripe.""" + recalculate_balances: NotRequired[PreviewUpdateRecalculateBalancesTypedDict] + r"""Controls whether balances should be recalculated during the subscription update.""" class PreviewUpdateParams(BaseModel): @@ -688,6 +704,9 @@ class PreviewUpdateParams(BaseModel): no_billing_changes: Optional[bool] = None r"""If true, the subscription is updated internally without applying billing changes in Stripe.""" + recalculate_balances: Optional[PreviewUpdateRecalculateBalances] = None + r"""Controls whether balances should be recalculated during the subscription update.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): optional_fields = set( @@ -703,6 +722,7 @@ def serialize_model(self, handler): "subscription_id", "cancel_action", "no_billing_changes", + "recalculate_balances", ] ) serialized = handler(self) @@ -710,7 +730,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -743,7 +763,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -829,7 +849,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -870,7 +890,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -956,7 +976,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1025,7 +1045,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1098,6 +1118,10 @@ class PreviewUpdateIncomingTypedDict(TypedDict): r"""The feature quantity selections associated with this plan change.""" effective_at: Nullable[float] r"""When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately.""" + canceled_at: Nullable[float] + r"""When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled.""" + expires_at: Nullable[float] + r"""When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire.""" plan: NotRequired[PlanTypedDict] @@ -1111,18 +1135,24 @@ class PreviewUpdateIncoming(BaseModel): effective_at: Nullable[float] r"""When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately.""" + canceled_at: Nullable[float] + r"""When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled.""" + + expires_at: Nullable[float] + r"""When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire.""" + plan: Optional[Plan] = None @model_serializer(mode="wrap") def serialize_model(self, handler): optional_fields = set(["plan"]) - nullable_fields = set(["effective_at"]) + nullable_fields = set(["effective_at", "canceled_at", "expires_at"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1161,6 +1191,10 @@ class PreviewUpdateOutgoingTypedDict(TypedDict): r"""The feature quantity selections associated with this plan change.""" effective_at: Nullable[float] r"""When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately.""" + canceled_at: Nullable[float] + r"""When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled.""" + expires_at: Nullable[float] + r"""When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire.""" plan: NotRequired[PlanTypedDict] @@ -1174,18 +1208,24 @@ class PreviewUpdateOutgoing(BaseModel): effective_at: Nullable[float] r"""When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately.""" + canceled_at: Nullable[float] + r"""When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled.""" + + expires_at: Nullable[float] + r"""When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire.""" + plan: Optional[Plan] = None @model_serializer(mode="wrap") def serialize_model(self, handler): optional_fields = set(["plan"]) - nullable_fields = set(["effective_at"]) + nullable_fields = set(["effective_at", "canceled_at", "expires_at"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1279,7 +1319,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/redeemreferralcodeop.py b/others/python-sdk/src/autumn_sdk/models/redeemreferralcodeop.py index 2e67e9b95..6f1c6e085 100644 --- a/others/python-sdk/src/autumn_sdk/models/redeemreferralcodeop.py +++ b/others/python-sdk/src/autumn_sdk/models/redeemreferralcodeop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/setuppaymentop.py b/others/python-sdk/src/autumn_sdk/models/setuppaymentop.py index 195e95672..a3f5643d5 100644 --- a/others/python-sdk/src/autumn_sdk/models/setuppaymentop.py +++ b/others/python-sdk/src/autumn_sdk/models/setuppaymentop.py @@ -34,7 +34,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -74,7 +74,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -125,7 +125,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -174,7 +174,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -210,7 +210,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -306,7 +306,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -391,7 +391,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -453,7 +453,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -501,7 +501,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -542,7 +542,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -592,7 +592,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -642,7 +642,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -677,7 +677,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -790,7 +790,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -830,7 +830,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/trackop.py b/others/python-sdk/src/autumn_sdk/models/trackop.py index e3581f413..a0e92d5bc 100644 --- a/others/python-sdk/src/autumn_sdk/models/trackop.py +++ b/others/python-sdk/src/autumn_sdk/models/trackop.py @@ -30,7 +30,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -69,7 +69,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -125,7 +125,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -181,7 +181,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/updatebalanceop.py b/others/python-sdk/src/autumn_sdk/models/updatebalanceop.py index d69bad2bc..39682e946 100644 --- a/others/python-sdk/src/autumn_sdk/models/updatebalanceop.py +++ b/others/python-sdk/src/autumn_sdk/models/updatebalanceop.py @@ -28,7 +28,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -124,7 +124,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/updatecustomerop.py b/others/python-sdk/src/autumn_sdk/models/updatecustomerop.py index 677b65cdf..0b4d17dba 100644 --- a/others/python-sdk/src/autumn_sdk/models/updatecustomerop.py +++ b/others/python-sdk/src/autumn_sdk/models/updatecustomerop.py @@ -37,7 +37,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -86,7 +86,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -132,7 +132,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -168,7 +168,60 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +UpdateCustomerThresholdTypeRequestBody = Literal[ + "usage", + "usage_percentage", +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class UpdateCustomerUsageAlertRequestBodyTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: UpdateCustomerThresholdTypeRequestBody + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class UpdateCustomerUsageAlertRequestBody(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: UpdateCustomerThresholdTypeRequestBody + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -184,6 +237,8 @@ class UpdateCustomerBillingControlsRequestTypedDict(TypedDict): r"""List of auto top-up configurations per feature.""" spend_limits: NotRequired[List[UpdateCustomerSpendLimitRequestTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[UpdateCustomerUsageAlertRequestBodyTypedDict]] + r"""List of usage alert configurations per feature.""" class UpdateCustomerBillingControlsRequest(BaseModel): @@ -195,15 +250,18 @@ class UpdateCustomerBillingControlsRequest(BaseModel): spend_limits: Optional[List[UpdateCustomerSpendLimitRequest]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[UpdateCustomerUsageAlertRequestBody]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["auto_topups", "spend_limits"]) + optional_fields = set(["auto_topups", "spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -281,7 +339,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -351,7 +409,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -397,7 +455,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -433,7 +491,63 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +UpdateCustomerThresholdTypeResponse = Union[ + Literal[ + "usage", + "usage_percentage", + ], + UnrecognizedStr, +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class UpdateCustomerUsageAlertResponseTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: UpdateCustomerThresholdTypeResponse + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class UpdateCustomerUsageAlertResponse(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: UpdateCustomerThresholdTypeResponse + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -449,6 +563,8 @@ class UpdateCustomerBillingControlsResponseTypedDict(TypedDict): r"""List of auto top-up configurations per feature.""" spend_limits: NotRequired[List[UpdateCustomerSpendLimitResponseTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[UpdateCustomerUsageAlertResponseTypedDict]] + r"""List of usage alert configurations per feature.""" class UpdateCustomerBillingControlsResponse(BaseModel): @@ -460,15 +576,18 @@ class UpdateCustomerBillingControlsResponse(BaseModel): spend_limits: Optional[List[UpdateCustomerSpendLimitResponse]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[UpdateCustomerUsageAlertResponse]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["auto_topups", "spend_limits"]) + optional_fields = set(["auto_topups", "spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -576,7 +695,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -629,7 +748,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -699,7 +818,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -772,7 +891,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -819,7 +938,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -921,7 +1040,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: m[k] = val diff --git a/others/python-sdk/src/autumn_sdk/models/updateentityop.py b/others/python-sdk/src/autumn_sdk/models/updateentityop.py index 3f7d90a4b..8ac0c6834 100644 --- a/others/python-sdk/src/autumn_sdk/models/updateentityop.py +++ b/others/python-sdk/src/autumn_sdk/models/updateentityop.py @@ -37,7 +37,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -73,7 +73,60 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +UpdateEntityThresholdTypeRequestBody = Literal[ + "usage", + "usage_percentage", +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class UpdateEntityUsageAlertRequestBodyTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: UpdateEntityThresholdTypeRequestBody + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class UpdateEntityUsageAlertRequestBody(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: UpdateEntityThresholdTypeRequestBody + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -87,6 +140,8 @@ class UpdateEntityBillingControlsRequestTypedDict(TypedDict): spend_limits: NotRequired[List[UpdateEntitySpendLimitRequestTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[UpdateEntityUsageAlertRequestBodyTypedDict]] + r"""List of usage alert configurations per feature.""" class UpdateEntityBillingControlsRequest(BaseModel): @@ -95,15 +150,18 @@ class UpdateEntityBillingControlsRequest(BaseModel): spend_limits: Optional[List[UpdateEntitySpendLimitRequest]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[UpdateEntityUsageAlertRequestBody]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["spend_limits"]) + optional_fields = set(["spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -139,7 +197,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -257,7 +315,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -310,7 +368,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -380,7 +438,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -453,7 +511,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -500,7 +558,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -544,7 +602,63 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) + + if val != UNSET_SENTINEL: + if val is not None or k not in optional_fields: + m[k] = val + + return m + + +UpdateEntityThresholdTypeResponse = Union[ + Literal[ + "usage", + "usage_percentage", + ], + UnrecognizedStr, +] +r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + +class UpdateEntityUsageAlertResponseTypedDict(TypedDict): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + threshold_type: UpdateEntityThresholdTypeResponse + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + feature_id: NotRequired[str] + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + enabled: NotRequired[bool] + r"""Whether this usage alert is enabled.""" + name: NotRequired[str] + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + +class UpdateEntityUsageAlertResponse(BaseModel): + threshold: float + r"""The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).""" + + threshold_type: UpdateEntityThresholdTypeResponse + r"""Whether the threshold is an absolute usage count or a percentage of the usage allowance.""" + + feature_id: Optional[str] = None + r"""The feature ID this alert applies to. If omitted, the alert applies globally.""" + + enabled: Optional[bool] = True + r"""Whether this usage alert is enabled.""" + + name: Optional[str] = None + r"""Optional user-defined label to distinguish multiple alerts on the same feature.""" + + @model_serializer(mode="wrap") + def serialize_model(self, handler): + optional_fields = set(["feature_id", "enabled", "name"]) + serialized = handler(self) + m = {} + + for n, f in type(self).model_fields.items(): + k = f.alias or n + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -558,6 +672,8 @@ class UpdateEntityBillingControlsResponseTypedDict(TypedDict): spend_limits: NotRequired[List[UpdateEntitySpendLimitResponseTypedDict]] r"""List of overage spend limits per feature.""" + usage_alerts: NotRequired[List[UpdateEntityUsageAlertResponseTypedDict]] + r"""List of usage alert configurations per feature.""" class UpdateEntityBillingControlsResponse(BaseModel): @@ -566,15 +682,18 @@ class UpdateEntityBillingControlsResponse(BaseModel): spend_limits: Optional[List[UpdateEntitySpendLimitResponse]] = None r"""List of overage spend limits per feature.""" + usage_alerts: Optional[List[UpdateEntityUsageAlertResponse]] = None + r"""List of usage alert configurations per feature.""" + @model_serializer(mode="wrap") def serialize_model(self, handler): - optional_fields = set(["spend_limits"]) + optional_fields = set(["spend_limits", "usage_alerts"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -631,7 +750,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -719,7 +838,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/models/updatefeatureop.py b/others/python-sdk/src/autumn_sdk/models/updatefeatureop.py index 9507c8601..d21c033e2 100644 --- a/others/python-sdk/src/autumn_sdk/models/updatefeatureop.py +++ b/others/python-sdk/src/autumn_sdk/models/updatefeatureop.py @@ -35,7 +35,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -144,7 +144,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -206,7 +206,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -279,7 +279,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: diff --git a/others/python-sdk/src/autumn_sdk/models/updateplanop.py b/others/python-sdk/src/autumn_sdk/models/updateplanop.py index bec31e36d..0ba234f79 100644 --- a/others/python-sdk/src/autumn_sdk/models/updateplanop.py +++ b/others/python-sdk/src/autumn_sdk/models/updateplanop.py @@ -35,7 +35,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -86,7 +86,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -135,7 +135,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -171,7 +171,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -267,7 +267,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -352,7 +352,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -414,7 +414,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -462,7 +462,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -552,7 +552,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -609,7 +609,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -650,7 +650,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -749,7 +749,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -805,7 +805,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -898,7 +898,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -941,7 +941,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -992,7 +992,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1062,7 +1062,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member @@ -1184,7 +1184,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) if val != UNSET_SENTINEL: if val is not None or k not in optional_fields: @@ -1283,7 +1283,7 @@ def serialize_model(self, handler): for n, f in type(self).model_fields.items(): k = f.alias or n - val = serialized.get(k, serialized.get(n)) + val = serialized.get(k) is_nullable_and_explicitly_set = ( k in nullable_fields and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member diff --git a/others/python-sdk/src/autumn_sdk/sdk.py b/others/python-sdk/src/autumn_sdk/sdk.py index 2a22c67c7..b31b126ba 100644 --- a/others/python-sdk/src/autumn_sdk/sdk.py +++ b/others/python-sdk/src/autumn_sdk/sdk.py @@ -52,8 +52,8 @@ def __init__( secret_key: Union[str, Callable[[], str]], x_api_version: Optional[str] = None, server_idx: Optional[int] = None, - url_params: Optional[Dict[str, str]] = None, server_url: Optional[str] = None, + url_params: Optional[Dict[str, str]] = None, client: Optional[HttpClient] = None, async_client: Optional[AsyncHttpClient] = None, retry_config: OptionalNullable[RetryConfig] = UNSET, diff --git a/others/python-sdk/src/autumn_sdk/utils/__init__.py b/others/python-sdk/src/autumn_sdk/utils/__init__.py index 0498cb8da..15394a08a 100644 --- a/others/python-sdk/src/autumn_sdk/utils/__init__.py +++ b/others/python-sdk/src/autumn_sdk/utils/__init__.py @@ -1,9 +1,10 @@ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" -from typing import Any, TYPE_CHECKING, Callable, TypeVar +from typing import TYPE_CHECKING, Callable, TypeVar +from importlib import import_module import asyncio - -from .dynamic_imports import lazy_getattr, lazy_dir +import builtins +import sys _T = TypeVar("_T") @@ -165,11 +166,38 @@ async def run_sync_in_thread(func: Callable[..., _T], *args) -> _T: } -def __getattr__(attr_name: str) -> Any: - return lazy_getattr( - attr_name, package=__package__, dynamic_imports=_dynamic_imports - ) +def dynamic_import(modname, retries=3): + for attempt in range(retries): + try: + return import_module(modname, __package__) + except KeyError: + # Clear any half-initialized module and retry + sys.modules.pop(modname, None) + if attempt == retries - 1: + break + raise KeyError(f"Failed to import module '{modname}' after {retries} attempts") + + +def __getattr__(attr_name: str) -> object: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"no {attr_name} found in _dynamic_imports, module name -> {__name__} " + ) + + try: + module = dynamic_import(module_name) + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e def __dir__(): - return lazy_dir(dynamic_imports=_dynamic_imports) + lazy_attrs = builtins.list(_dynamic_imports.keys()) + return builtins.sorted(lazy_attrs) diff --git a/others/python-sdk/src/autumn_sdk/utils/dynamic_imports.py b/others/python-sdk/src/autumn_sdk/utils/dynamic_imports.py deleted file mode 100644 index 673edf82a..000000000 --- a/others/python-sdk/src/autumn_sdk/utils/dynamic_imports.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.""" - -from importlib import import_module -import builtins -import sys - - -def dynamic_import(package, modname, retries=3): - """Import a module relative to package, retrying on KeyError from half-initialized modules.""" - for attempt in range(retries): - try: - return import_module(modname, package) - except KeyError: - sys.modules.pop(modname, None) - if attempt == retries - 1: - break - raise KeyError(f"Failed to import module '{modname}' after {retries} attempts") - - -def lazy_getattr(attr_name, *, package, dynamic_imports, sub_packages=None): - """Module-level __getattr__ that lazily loads from a dynamic_imports mapping. - - Args: - attr_name: The attribute being looked up. - package: The caller's __package__ (for relative imports). - dynamic_imports: Dict mapping attribute names to relative module paths. - sub_packages: Optional list of subpackage names to lazy-load. - """ - module_name = dynamic_imports.get(attr_name) - if module_name is not None: - try: - module = dynamic_import(package, module_name) - return getattr(module, attr_name) - except ImportError as e: - raise ImportError( - f"Failed to import {attr_name} from {module_name}: {e}" - ) from e - except AttributeError as e: - raise AttributeError( - f"Failed to get {attr_name} from {module_name}: {e}" - ) from e - - if sub_packages and attr_name in sub_packages: - return import_module(f".{attr_name}", package) - - raise AttributeError(f"module '{package}' has no attribute '{attr_name}'") - - -def lazy_dir(*, dynamic_imports, sub_packages=None): - """Module-level __dir__ that lists lazily-loadable attributes.""" - lazy_attrs = builtins.list(dynamic_imports.keys()) - if sub_packages: - lazy_attrs.extend(sub_packages) - return builtins.sorted(lazy_attrs) diff --git a/others/python-sdk/src/autumn_sdk/utils/eventstreaming.py b/others/python-sdk/src/autumn_sdk/utils/eventstreaming.py index 3bdcd6d3d..f2052fc22 100644 --- a/others/python-sdk/src/autumn_sdk/utils/eventstreaming.py +++ b/others/python-sdk/src/autumn_sdk/utils/eventstreaming.py @@ -32,12 +32,9 @@ def __init__( decoder: Callable[[str], T], sentinel: Optional[str] = None, client_ref: Optional[object] = None, - data_required: bool = True, ): self.response = response - self.generator = stream_events( - response, decoder, sentinel, data_required=data_required - ) + self.generator = stream_events(response, decoder, sentinel) self.client_ref = client_ref self._closed = False @@ -71,12 +68,9 @@ def __init__( decoder: Callable[[str], T], sentinel: Optional[str] = None, client_ref: Optional[object] = None, - data_required: bool = True, ): self.response = response - self.generator = stream_events_async( - response, decoder, sentinel, data_required=data_required - ) + self.generator = stream_events_async(response, decoder, sentinel) self.client_ref = client_ref self._closed = False @@ -122,7 +116,6 @@ async def stream_events_async( response: httpx.Response, decoder: Callable[[str], T], sentinel: Optional[str] = None, - data_required: bool = True, ) -> AsyncGenerator[T, None]: buffer = bytearray() position = 0 @@ -145,11 +138,7 @@ async def stream_events_async( block = buffer[position:i] position = i + len(seq) event, discard, event_id = _parse_event( - raw=block, - decoder=decoder, - sentinel=sentinel, - event_id=event_id, - data_required=data_required, + raw=block, decoder=decoder, sentinel=sentinel, event_id=event_id ) if event is not None: yield event @@ -162,11 +151,7 @@ async def stream_events_async( position = 0 event, discard, _ = _parse_event( - raw=buffer, - decoder=decoder, - sentinel=sentinel, - event_id=event_id, - data_required=data_required, + raw=buffer, decoder=decoder, sentinel=sentinel, event_id=event_id ) if event is not None: yield event @@ -176,7 +161,6 @@ def stream_events( response: httpx.Response, decoder: Callable[[str], T], sentinel: Optional[str] = None, - data_required: bool = True, ) -> Generator[T, None, None]: buffer = bytearray() position = 0 @@ -199,11 +183,7 @@ def stream_events( block = buffer[position:i] position = i + len(seq) event, discard, event_id = _parse_event( - raw=block, - decoder=decoder, - sentinel=sentinel, - event_id=event_id, - data_required=data_required, + raw=block, decoder=decoder, sentinel=sentinel, event_id=event_id ) if event is not None: yield event @@ -216,11 +196,7 @@ def stream_events( position = 0 event, discard, _ = _parse_event( - raw=buffer, - decoder=decoder, - sentinel=sentinel, - event_id=event_id, - data_required=data_required, + raw=buffer, decoder=decoder, sentinel=sentinel, event_id=event_id ) if event is not None: yield event @@ -232,7 +208,6 @@ def _parse_event( decoder: Callable[[str], T], sentinel: Optional[str] = None, event_id: Optional[str] = None, - data_required: bool = True, ) -> Tuple[Optional[T], bool, Optional[str]]: block = raw.decode() lines = re.split(r"\r?\n|\r", block) @@ -275,10 +250,6 @@ def _parse_event( if sentinel and data == f"{sentinel}\n": return None, True, event_id - # Skip data-less events when data is required - if not data and publish and data_required: - return None, False, event_id - if data: data = data[:-1] try: diff --git a/others/python-sdk/src/autumn_sdk/utils/retries.py b/others/python-sdk/src/autumn_sdk/utils/retries.py index af07d4e94..88a91b10c 100644 --- a/others/python-sdk/src/autumn_sdk/utils/retries.py +++ b/others/python-sdk/src/autumn_sdk/utils/retries.py @@ -144,7 +144,12 @@ def do_request() -> httpx.Response: if res.status_code == parsed_code: raise TemporaryError(res) - except (httpx.NetworkError, httpx.TimeoutException) as exception: + except httpx.ConnectError as exception: + if retries.config.retry_connection_errors: + raise + + raise PermanentError(exception) from exception + except httpx.TimeoutException as exception: if retries.config.retry_connection_errors: raise @@ -188,7 +193,12 @@ async def do_request() -> httpx.Response: if res.status_code == parsed_code: raise TemporaryError(res) - except (httpx.NetworkError, httpx.TimeoutException) as exception: + except httpx.ConnectError as exception: + if retries.config.retry_connection_errors: + raise + + raise PermanentError(exception) from exception + except httpx.TimeoutException as exception: if retries.config.retry_connection_errors: raise diff --git a/package.json b/package.json index caf6fa971..1fecf8082 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,8 @@ "knip:fix-all": "knip --fix --allow-remove-files", "prepare": "husky", "api": "cd packages/openapi && bun generate", + "svix:push": "infisical run --env=dev -- bun packages/openapi/scripts/svixPush.ts", + "svix:push:prod": "infisical run --env=prod -- bun packages/openapi/scripts/svixPush.ts", "docs:pull": "bun -F @autumn/docs pull", "docs": "bun -F @autumn/docs dev", "docs:build": "bun -F @autumn/docs build", diff --git a/packages/autumn-js/src/generated/getOrCreateCustomerSchemas.ts b/packages/autumn-js/src/generated/getOrCreateCustomerSchemas.ts index d91e98d4f..6c355b30c 100644 --- a/packages/autumn-js/src/generated/getOrCreateCustomerSchemas.ts +++ b/packages/autumn-js/src/generated/getOrCreateCustomerSchemas.ts @@ -33,6 +33,14 @@ export const getOrCreateCustomerSpendLimitOutboundSchema = z.object({ overage_limit: z.union([z.number(), z.undefined()]).optional(), }); +export const getOrCreateCustomerUsageAlertOutboundSchema = z.object({ + feature_id: z.union([z.string(), z.undefined()]).optional(), + enabled: z.boolean(), + threshold: z.number(), + threshold_type: z.string(), + name: z.union([z.string(), z.undefined()]).optional(), +}); + export const getOrCreateCustomerBillingControlsOutboundSchema = z.object({ auto_topups: z .union([z.array(getOrCreateCustomerAutoTopupOutboundSchema), z.undefined()]) @@ -43,6 +51,12 @@ export const getOrCreateCustomerBillingControlsOutboundSchema = z.object({ z.undefined(), ]) .optional(), + usage_alerts: z + .union([ + z.array(getOrCreateCustomerUsageAlertOutboundSchema), + z.undefined(), + ]) + .optional(), }); export const getOrCreateCustomerParamsOutboundSchema = z.object({ @@ -84,6 +98,16 @@ export const getOrCreateCustomerAutoTopupSchema = z.object({ .optional(), }); +export const getOrCreateCustomerThresholdTypeSchema = closedEnumSchema; + +export const getOrCreateCustomerUsageAlertSchema = z.object({ + featureId: z.union([z.string(), z.undefined()]).optional(), + enabled: z.union([z.boolean(), z.undefined()]).optional(), + threshold: z.number(), + thresholdType: getOrCreateCustomerThresholdTypeSchema, + name: z.union([z.string(), z.undefined()]).optional(), +}); + export const getOrCreateCustomerBillingControlsSchema = z.object({ autoTopups: z .union([z.array(getOrCreateCustomerAutoTopupSchema), z.undefined()]) @@ -91,6 +115,9 @@ export const getOrCreateCustomerBillingControlsSchema = z.object({ spendLimits: z .union([z.array(getOrCreateCustomerSpendLimitSchema), z.undefined()]) .optional(), + usageAlerts: z + .union([z.array(getOrCreateCustomerUsageAlertSchema), z.undefined()]) + .optional(), }); export const getOrCreateCustomerParamsSchema = z.object({ diff --git a/packages/autumn-js/src/generated/multiAttachSchemas.ts b/packages/autumn-js/src/generated/multiAttachSchemas.ts index cd501de02..8a1bea4ff 100644 --- a/packages/autumn-js/src/generated/multiAttachSchemas.ts +++ b/packages/autumn-js/src/generated/multiAttachSchemas.ts @@ -36,20 +36,6 @@ export const multiAttachSpendLimitSchema = z.object({ overageLimit: z.union([z.number(), z.undefined()]).optional(), }); -export const multiAttachBillingControlsSchema = z.object({ - spendLimits: z - .union([z.array(multiAttachSpendLimitSchema), z.undefined()]) - .optional(), -}); - -export const multiAttachEntityDataSchema = z.object({ - featureId: z.string(), - name: z.union([z.string(), z.undefined()]).optional(), - billingControls: z - .union([multiAttachBillingControlsSchema, z.undefined()]) - .optional(), -}); - export const multiAttachInvoiceSchema = z.object({ status: z.string().nullable(), stripeId: z.string(), @@ -166,10 +152,21 @@ export const multiAttachSpendLimitOutboundSchema = z.object({ overage_limit: z.union([z.number(), z.undefined()]).optional(), }); +export const multiAttachUsageAlertOutboundSchema = z.object({ + feature_id: z.union([z.string(), z.undefined()]).optional(), + enabled: z.boolean(), + threshold: z.number(), + threshold_type: z.string(), + name: z.union([z.string(), z.undefined()]).optional(), +}); + export const multiAttachBillingControlsOutboundSchema = z.object({ spend_limits: z .union([z.array(multiAttachSpendLimitOutboundSchema), z.undefined()]) .optional(), + usage_alerts: z + .union([z.array(multiAttachUsageAlertOutboundSchema), z.undefined()]) + .optional(), }); export const multiAttachEntityDataOutboundSchema = z.object({ @@ -281,6 +278,33 @@ export const multiAttachFreeTrialParamsSchema = z.object({ export const multiAttachRedirectModeSchema = closedEnumSchema; +export const multiAttachThresholdTypeSchema = closedEnumSchema; + +export const multiAttachUsageAlertSchema = z.object({ + featureId: z.union([z.string(), z.undefined()]).optional(), + enabled: z.union([z.boolean(), z.undefined()]).optional(), + threshold: z.number(), + thresholdType: multiAttachThresholdTypeSchema, + name: z.union([z.string(), z.undefined()]).optional(), +}); + +export const multiAttachBillingControlsSchema = z.object({ + spendLimits: z + .union([z.array(multiAttachSpendLimitSchema), z.undefined()]) + .optional(), + usageAlerts: z + .union([z.array(multiAttachUsageAlertSchema), z.undefined()]) + .optional(), +}); + +export const multiAttachEntityDataSchema = z.object({ + featureId: z.string(), + name: z.union([z.string(), z.undefined()]).optional(), + billingControls: z + .union([multiAttachBillingControlsSchema, z.undefined()]) + .optional(), +}); + export const multiAttachParamsSchema = z.object({ customerId: z.string(), entityId: z.union([z.string(), z.undefined()]).optional(), diff --git a/packages/autumn-js/src/generated/previewAttachSchemas.ts b/packages/autumn-js/src/generated/previewAttachSchemas.ts index 5ae0bb781..8d47c706a 100644 --- a/packages/autumn-js/src/generated/previewAttachSchemas.ts +++ b/packages/autumn-js/src/generated/previewAttachSchemas.ts @@ -425,6 +425,8 @@ export const previewAttachIncomingSchema = z.object({ plan: z.union([planSchema, z.undefined()]).optional(), featureQuantities: z.array(previewAttachIncomingFeatureQuantitySchema), effectiveAt: z.number().nullable(), + canceledAt: z.number().nullable(), + expiresAt: z.number().nullable(), }); export const previewAttachOutgoingSchema = z.object({ @@ -432,6 +434,8 @@ export const previewAttachOutgoingSchema = z.object({ plan: z.union([planSchema, z.undefined()]).optional(), featureQuantities: z.array(previewAttachOutgoingFeatureQuantitySchema), effectiveAt: z.number().nullable(), + canceledAt: z.number().nullable(), + expiresAt: z.number().nullable(), }); export const previewAttachResponseSchema = z.object({ diff --git a/packages/autumn-js/src/generated/previewMultiAttachSchemas.ts b/packages/autumn-js/src/generated/previewMultiAttachSchemas.ts index 1cad6613d..72c523cea 100644 --- a/packages/autumn-js/src/generated/previewMultiAttachSchemas.ts +++ b/packages/autumn-js/src/generated/previewMultiAttachSchemas.ts @@ -36,20 +36,6 @@ export const previewMultiAttachSpendLimitSchema = z.object({ overageLimit: z.union([z.number(), z.undefined()]).optional(), }); -export const previewMultiAttachBillingControlsSchema = z.object({ - spendLimits: z - .union([z.array(previewMultiAttachSpendLimitSchema), z.undefined()]) - .optional(), -}); - -export const previewMultiAttachEntityDataSchema = z.object({ - featureId: z.string(), - name: z.union([z.string(), z.undefined()]).optional(), - billingControls: z - .union([previewMultiAttachBillingControlsSchema, z.undefined()]) - .optional(), -}); - export const previewMultiAttachDiscountSchema = z.object({ amountOff: z.number(), percentOff: z.union([z.number(), z.undefined()]).optional(), @@ -256,10 +242,21 @@ export const previewMultiAttachSpendLimitOutboundSchema = z.object({ overage_limit: z.union([z.number(), z.undefined()]).optional(), }); +export const previewMultiAttachUsageAlertOutboundSchema = z.object({ + feature_id: z.union([z.string(), z.undefined()]).optional(), + enabled: z.boolean(), + threshold: z.number(), + threshold_type: z.string(), + name: z.union([z.string(), z.undefined()]).optional(), +}); + export const previewMultiAttachBillingControlsOutboundSchema = z.object({ spend_limits: z .union([z.array(previewMultiAttachSpendLimitOutboundSchema), z.undefined()]) .optional(), + usage_alerts: z + .union([z.array(previewMultiAttachUsageAlertOutboundSchema), z.undefined()]) + .optional(), }); export const previewMultiAttachEntityDataOutboundSchema = z.object({ @@ -382,6 +379,33 @@ export const previewMultiAttachFreeTrialParamsSchema = z.object({ export const previewMultiAttachRedirectModeSchema = closedEnumSchema; +export const previewMultiAttachThresholdTypeSchema = closedEnumSchema; + +export const previewMultiAttachUsageAlertSchema = z.object({ + featureId: z.union([z.string(), z.undefined()]).optional(), + enabled: z.union([z.boolean(), z.undefined()]).optional(), + threshold: z.number(), + thresholdType: previewMultiAttachThresholdTypeSchema, + name: z.union([z.string(), z.undefined()]).optional(), +}); + +export const previewMultiAttachBillingControlsSchema = z.object({ + spendLimits: z + .union([z.array(previewMultiAttachSpendLimitSchema), z.undefined()]) + .optional(), + usageAlerts: z + .union([z.array(previewMultiAttachUsageAlertSchema), z.undefined()]) + .optional(), +}); + +export const previewMultiAttachEntityDataSchema = z.object({ + featureId: z.string(), + name: z.union([z.string(), z.undefined()]).optional(), + billingControls: z + .union([previewMultiAttachBillingControlsSchema, z.undefined()]) + .optional(), +}); + export const previewMultiAttachParamsSchema = z.object({ customerId: z.string(), entityId: z.union([z.string(), z.undefined()]).optional(), @@ -415,6 +439,8 @@ export const previewMultiAttachIncomingSchema = z.object({ plan: z.union([planSchema, z.undefined()]).optional(), featureQuantities: z.array(previewMultiAttachIncomingFeatureQuantitySchema), effectiveAt: z.number().nullable(), + canceledAt: z.number().nullable(), + expiresAt: z.number().nullable(), }); export const previewMultiAttachOutgoingSchema = z.object({ @@ -422,6 +448,8 @@ export const previewMultiAttachOutgoingSchema = z.object({ plan: z.union([planSchema, z.undefined()]).optional(), featureQuantities: z.array(previewMultiAttachOutgoingFeatureQuantitySchema), effectiveAt: z.number().nullable(), + canceledAt: z.number().nullable(), + expiresAt: z.number().nullable(), }); export const previewMultiAttachResponseSchema = z.object({ diff --git a/packages/autumn-js/src/generated/previewUpdateSubscriptionSchemas.ts b/packages/autumn-js/src/generated/previewUpdateSubscriptionSchemas.ts index 7078c7f24..b5bf1c187 100644 --- a/packages/autumn-js/src/generated/previewUpdateSubscriptionSchemas.ts +++ b/packages/autumn-js/src/generated/previewUpdateSubscriptionSchemas.ts @@ -25,6 +25,10 @@ export const previewUpdateInvoiceModeSchema = z.object({ finalize: z.union([z.boolean(), z.undefined()]).optional(), }); +export const previewUpdateRecalculateBalancesSchema = z.object({ + enabled: z.boolean(), +}); + export const previewUpdateDiscountSchema = z.object({ amountOff: z.number(), percentOff: z.union([z.number(), z.undefined()]).optional(), @@ -202,6 +206,10 @@ export const previewUpdateInvoiceModeOutboundSchema = z.object({ finalize: z.boolean(), }); +export const previewUpdateRecalculateBalancesOutboundSchema = z.object({ + enabled: z.boolean(), +}); + export const previewUpdateParamsOutboundSchema = z.object({ customer_id: z.string(), entity_id: z.union([z.string(), z.undefined()]).optional(), @@ -224,6 +232,9 @@ export const previewUpdateParamsOutboundSchema = z.object({ subscription_id: z.union([z.string(), z.undefined()]).optional(), cancel_action: z.union([z.string(), z.undefined()]).optional(), no_billing_changes: z.union([z.boolean(), z.undefined()]).optional(), + recalculate_balances: z + .union([previewUpdateRecalculateBalancesOutboundSchema, z.undefined()]) + .optional(), }); const closedEnumSchema = z.any(); @@ -346,6 +357,9 @@ export const previewUpdateParamsSchema = z.object({ .union([previewUpdateCancelActionSchema, z.undefined()]) .optional(), noBillingChanges: z.union([z.boolean(), z.undefined()]).optional(), + recalculateBalances: z + .union([previewUpdateRecalculateBalancesSchema, z.undefined()]) + .optional(), }); export const previewUpdateIncomingSchema = z.object({ @@ -353,6 +367,8 @@ export const previewUpdateIncomingSchema = z.object({ plan: z.union([planSchema, z.undefined()]).optional(), featureQuantities: z.array(previewUpdateIncomingFeatureQuantitySchema), effectiveAt: z.number().nullable(), + canceledAt: z.number().nullable(), + expiresAt: z.number().nullable(), }); export const previewUpdateOutgoingSchema = z.object({ @@ -360,6 +376,8 @@ export const previewUpdateOutgoingSchema = z.object({ plan: z.union([planSchema, z.undefined()]).optional(), featureQuantities: z.array(previewUpdateOutgoingFeatureQuantitySchema), effectiveAt: z.number().nullable(), + canceledAt: z.number().nullable(), + expiresAt: z.number().nullable(), }); export const intentSchema = openEnumSchema; diff --git a/packages/autumn-js/src/generated/updateSubscriptionSchemas.ts b/packages/autumn-js/src/generated/updateSubscriptionSchemas.ts index 609856c01..b99dca46b 100644 --- a/packages/autumn-js/src/generated/updateSubscriptionSchemas.ts +++ b/packages/autumn-js/src/generated/updateSubscriptionSchemas.ts @@ -25,6 +25,10 @@ export const billingUpdateInvoiceModeSchema = z.object({ finalize: z.union([z.boolean(), z.undefined()]).optional(), }); +export const billingUpdateRecalculateBalancesSchema = z.object({ + enabled: z.boolean(), +}); + export const billingUpdateInvoiceSchema = z.object({ status: z.string().nullable(), stripeId: z.string(), @@ -122,6 +126,10 @@ export const billingUpdateInvoiceModeOutboundSchema = z.object({ finalize: z.boolean(), }); +export const billingUpdateRecalculateBalancesOutboundSchema = z.object({ + enabled: z.boolean(), +}); + export const updateSubscriptionParamsOutboundSchema = z.object({ customer_id: z.string(), entity_id: z.union([z.string(), z.undefined()]).optional(), @@ -141,6 +149,9 @@ export const updateSubscriptionParamsOutboundSchema = z.object({ subscription_id: z.union([z.string(), z.undefined()]).optional(), cancel_action: z.union([z.string(), z.undefined()]).optional(), no_billing_changes: z.union([z.boolean(), z.undefined()]).optional(), + recalculate_balances: z + .union([billingUpdateRecalculateBalancesOutboundSchema, z.undefined()]) + .optional(), }); const closedEnumSchema = z.any(); @@ -261,6 +272,9 @@ export const updateSubscriptionParamsSchema = z.object({ .union([billingUpdateCancelActionSchema, z.undefined()]) .optional(), noBillingChanges: z.union([z.boolean(), z.undefined()]).optional(), + recalculateBalances: z + .union([billingUpdateRecalculateBalancesSchema, z.undefined()]) + .optional(), }); export const billingUpdateCodeSchema = openEnumSchema; diff --git a/packages/openapi/openapi-stripped.yml b/packages/openapi/openapi-stripped.yml index 49a935195..131c6dde6 100644 --- a/packages/openapi/openapi-stripped.yml +++ b/packages/openapi/openapi-stripped.yml @@ -86,6 +86,7 @@ components: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -122,6 +123,40 @@ components: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a percentage + (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) title: CustomerData description: Customer details to set when creating a customer @@ -137,6 +172,7 @@ components: - purchases.plan - balances.feature - flags.feature + type: string title: CustomerExpand Customer: type: object @@ -174,6 +210,7 @@ components: enum: - sandbox - live + type: string description: The environment this customer was created in. metadata: type: object @@ -216,6 +253,7 @@ components: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -252,6 +290,40 @@ components: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a percentage + (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) subscriptions: type: array @@ -279,6 +351,7 @@ components: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -406,6 +479,7 @@ components: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -543,6 +617,7 @@ components: enum: - sandbox - live + type: string description: The environment (sandbox/live) required: - id @@ -588,6 +663,7 @@ components: - fixed_discount - free_product - invoice_credits + type: string description: The type of reward discount_value: type: number @@ -597,6 +673,7 @@ components: - one_off - months - forever + type: string description: How long the discount lasts duration_value: anyOf: @@ -795,6 +872,7 @@ components: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -844,6 +922,7 @@ components: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -908,6 +987,7 @@ components: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. @@ -941,6 +1021,7 @@ components: enum: - graduated - volume + type: string interval: enum: - one_off @@ -949,6 +1030,7 @@ components: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -963,6 +1045,7 @@ components: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." max_purchase: @@ -1005,6 +1088,7 @@ components: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -1033,6 +1117,7 @@ components: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -1051,6 +1136,7 @@ components: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -1073,6 +1159,7 @@ components: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -1089,6 +1176,7 @@ components: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -1128,6 +1216,7 @@ components: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -1257,6 +1346,7 @@ components: - quarter - semi_annual - year + type: string - const: multiple description: The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals. @@ -1291,6 +1381,7 @@ components: enum: - graduated - volume + type: string description: "How tiers are applied: graduated (split across bands) or volume (flat rate for the matched tier)." billing_units: @@ -1300,6 +1391,7 @@ components: enum: - prepaid - usage_based + type: string description: Whether usage is prepaid or billed pay-per-use. max_purchase: anyOf: @@ -1476,6 +1568,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -1512,6 +1605,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) expand: type: array @@ -1591,6 +1718,7 @@ paths: enum: - active - scheduled + type: string description: Filter by customer product status. Defaults to active and scheduled search: type: string @@ -1648,6 +1776,7 @@ paths: enum: - sandbox - live + type: string description: The environment this customer was created in. metadata: type: object @@ -1690,6 +1819,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -1726,6 +1856,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this + is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) subscriptions: type: array @@ -1753,6 +1917,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -1881,6 +2046,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -2185,6 +2351,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -2221,6 +2388,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) new_customer_id: $ref: "#/components/schemas/CustomerId" @@ -2275,6 +2476,7 @@ paths: enum: - sandbox - live + type: string description: The environment this customer was created in. metadata: type: object @@ -2317,6 +2519,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -2353,6 +2556,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) subscriptions: type: array @@ -2380,6 +2617,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -2508,6 +2746,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -2758,6 +2997,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -2796,6 +3036,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -2832,6 +3073,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -2840,6 +3082,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -2855,6 +3098,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -2874,6 +3118,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -2882,6 +3127,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -2898,6 +3144,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -2924,6 +3171,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -3030,6 +3278,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -3079,6 +3328,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -3143,6 +3393,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. @@ -3176,6 +3427,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -3184,6 +3436,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -3199,6 +3452,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." @@ -3242,6 +3496,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -3270,6 +3525,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -3288,6 +3544,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -3310,6 +3567,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -3326,6 +3584,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -3483,6 +3742,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -3532,6 +3792,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -3596,6 +3857,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. @@ -3629,6 +3891,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -3637,6 +3900,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -3652,6 +3916,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." @@ -3695,6 +3960,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -3723,6 +3989,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -3741,6 +4008,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -3763,6 +4031,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -3779,6 +4048,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -3941,6 +4211,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -3990,6 +4261,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -4054,6 +4326,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units @@ -4088,6 +4361,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -4096,6 +4370,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -4111,6 +4386,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." @@ -4155,6 +4431,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -4184,6 +4461,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -4202,6 +4480,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -4224,6 +4503,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -4240,6 +4520,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -4371,6 +4652,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -4411,6 +4693,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -4447,6 +4730,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -4455,6 +4739,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -4470,6 +4755,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -4489,6 +4775,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -4497,6 +4784,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -4513,6 +4801,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -4540,6 +4829,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -4631,6 +4921,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -4680,6 +4971,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -4744,6 +5036,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. @@ -4777,6 +5070,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -4785,6 +5079,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -4800,6 +5095,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." @@ -4843,6 +5139,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -4871,6 +5168,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -4889,6 +5187,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -4911,6 +5210,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -4927,6 +5227,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -5075,6 +5376,7 @@ paths: - boolean - metered - credit_system + type: string description: The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. @@ -5160,6 +5462,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -5274,6 +5577,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -5378,6 +5682,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -5489,6 +5794,7 @@ paths: - boolean - metered - credit_system + type: string description: The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. @@ -5574,6 +5880,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -5761,6 +6068,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -5802,6 +6110,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -5838,6 +6147,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -5846,6 +6156,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -5861,6 +6172,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -5880,6 +6192,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -5888,6 +6201,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -5904,6 +6218,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -5930,6 +6245,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -5973,6 +6289,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -5981,6 +6298,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -6017,6 +6335,7 @@ paths: enum: - immediate - end_of_cycle + type: string description: When the plan change should take effect. 'immediate' applies now, 'end_of_cycle' schedules for the end of the current billing cycle. By default, upgrades are immediate and downgrades are @@ -6148,6 +6467,7 @@ paths: - 3ds_required - payment_method_required - payment_failed + type: string description: The type of action required to complete the payment. reason: type: string @@ -6221,6 +6541,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -6262,6 +6583,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -6298,6 +6620,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -6306,6 +6629,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -6322,6 +6646,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -6341,6 +6666,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -6349,6 +6675,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -6365,6 +6692,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -6423,6 +6751,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -6489,6 +6818,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -6529,6 +6859,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is + a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. required: - feature_id @@ -6605,6 +6969,7 @@ paths: - 3ds_required - payment_method_required - payment_failed + type: string description: The type of action required to complete the payment. reason: type: string @@ -6703,6 +7068,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -6744,6 +7110,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -6780,6 +7147,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -6788,6 +7156,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -6803,6 +7172,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -6822,6 +7192,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -6830,6 +7201,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -6846,6 +7218,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -6872,6 +7245,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -6915,6 +7289,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -6923,6 +7298,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -6959,6 +7335,7 @@ paths: enum: - immediate - end_of_cycle + type: string description: When the plan change should take effect. 'immediate' applies now, 'end_of_cycle' schedules for the end of the current billing cycle. By default, upgrades are immediate and downgrades are @@ -7284,10 +7661,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. - required: + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. + required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being added or updated. outgoing: type: array @@ -7321,10 +7712,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being removed or ended. required: - customer_id @@ -7402,6 +7807,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -7443,6 +7849,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -7479,6 +7886,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -7487,6 +7895,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -7503,6 +7912,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -7522,6 +7932,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -7530,6 +7941,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -7546,6 +7958,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -7604,6 +8017,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -7670,6 +8084,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -7710,6 +8125,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is + a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. required: - feature_id @@ -7980,10 +8429,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being added or updated. outgoing: type: array @@ -8017,10 +8480,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being removed or ended. required: - customer_id @@ -8119,6 +8596,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -8160,6 +8638,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -8196,6 +8675,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -8204,6 +8684,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -8219,6 +8700,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -8238,6 +8720,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -8246,6 +8729,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -8262,6 +8746,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -8288,6 +8773,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -8331,6 +8817,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -8339,6 +8826,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -8353,6 +8841,7 @@ paths: - cancel_immediately - cancel_end_of_cycle - uncancel + type: string description: Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. @@ -8360,6 +8849,17 @@ paths: type: boolean description: If true, the subscription is updated internally without applying billing changes in Stripe. + recalculate_balances: + type: object + properties: + enabled: + type: boolean + description: If true, recalculates balances during the subscription update. Only + applicable when updating feature quantities. + required: + - enabled + description: Controls whether balances should be recalculated during the + subscription update. required: - customer_id title: UpdateSubscriptionParams @@ -8430,6 +8930,7 @@ paths: - 3ds_required - payment_method_required - payment_failed + type: string description: The type of action required to complete the payment. reason: type: string @@ -8529,6 +9030,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -8570,6 +9072,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -8606,6 +9109,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -8614,6 +9118,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -8629,6 +9134,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -8648,6 +9154,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -8656,6 +9163,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -8672,6 +9180,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -8698,6 +9207,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -8741,6 +9251,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -8749,6 +9260,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -8763,6 +9275,7 @@ paths: - cancel_immediately - cancel_end_of_cycle - uncancel + type: string description: Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. @@ -8770,6 +9283,17 @@ paths: type: boolean description: If true, the subscription is updated internally without applying billing changes in Stripe. + recalculate_balances: + type: object + properties: + enabled: + type: boolean + description: If true, recalculates balances during the subscription update. Only + applicable when updating feature quantities. + required: + - enabled + description: Controls whether balances should be recalculated during the + subscription update. required: - customer_id title: PreviewUpdateParams @@ -9034,10 +9558,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being added or updated. outgoing: type: array @@ -9071,10 +9609,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being removed or ended. intent: enum: @@ -9084,6 +9636,7 @@ paths: - cancel_end_of_cycle - uncancel - none + type: string required: - customer_id - line_items @@ -9235,6 +9788,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -9276,6 +9830,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -9312,6 +9867,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -9320,6 +9876,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -9335,6 +9892,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -9354,6 +9912,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -9362,6 +9921,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -9378,6 +9938,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -9404,6 +9965,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -9424,6 +9986,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -9592,6 +10155,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the balance resets (e.g., 'month', 'day', 'year'). interval_count: @@ -9684,6 +10248,7 @@ paths: - quarter - semi_annual - year + type: string description: Target a specific balance by its reset interval. Use when the customer has multiple balances for the same feature with different reset intervals. @@ -9762,6 +10327,7 @@ paths: - quarter - semi_annual - year + type: string description: Target a specific balance by its reset interval. Use when the customer has multiple balances for the same feature with different reset intervals. @@ -9809,6 +10375,7 @@ paths: enum: - confirm - release + type: string description: Use 'confirm' to commit the deduction, or 'release' to return the held balance. override_value: @@ -9994,6 +10561,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -10068,6 +10636,7 @@ paths: enum: - usage_limit - feature_flag + type: string description: The reason access was denied. 'usage_limit' means the customer exceeded their balance, 'feature_flag' means the feature is not included in their plan. @@ -10103,6 +10672,7 @@ paths: enum: - sandbox - live + type: string description: The environment of the product is_add_on: type: boolean @@ -10132,6 +10702,7 @@ paths: - feature - priced_feature - price + type: string - type: "null" description: The type of the product item feature_id: @@ -10147,6 +10718,7 @@ paths: - continuous_use - boolean - static + type: string - type: "null" description: Single use features are used once and then depleted, like API calls or credits. Continuous use features are @@ -10170,6 +10742,7 @@ paths: - quarter - semi_annual - year + type: string - type: "null" description: The reset or billing interval of the product item. If null, feature will have no reset date, and if there's a @@ -10200,6 +10773,7 @@ paths: - enum: - graduated - volume + type: string - type: "null" description: "How tiers are applied: graduated (split across bands) or volume (flat rate for the matched tier). Defaults @@ -10209,6 +10783,7 @@ paths: - enum: - prepaid - pay_per_use + type: string - type: "null" description: Whether the feature should be prepaid upfront or billed for how much they use end of billing period. @@ -10269,6 +10844,7 @@ paths: enum: - month - forever + type: string default: month length: type: number @@ -10283,6 +10859,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string - type: "null" on_decrease: anyOf: @@ -10292,6 +10869,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string - type: "null" - type: "null" description: Configuration for rollover and proration behavior of the feature. @@ -10307,6 +10885,7 @@ paths: - day - month - year + type: string description: The duration type of the free trial length: type: number @@ -10351,6 +10930,7 @@ paths: - cancel - expired - past_due + type: string description: Scenario for when this product is used in attach flows properties: type: object @@ -10767,6 +11347,7 @@ paths: - last_cycle - 1bc - 3bc + type: string description: Time range to aggregate events for. Either range or custom_range must be provided bin_size: @@ -10774,6 +11355,7 @@ paths: - day - hour - month + type: string default: day description: Size of the time bins to aggregate events for. Defaults to hour if range is 24h, otherwise day @@ -10964,6 +11546,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. customer_data: $ref: "#/components/schemas/CustomerData" @@ -11022,6 +11638,7 @@ paths: enum: - sandbox - live + type: string description: The environment (sandbox/live) subscriptions: type: array @@ -11049,6 +11666,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -11172,6 +11790,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -11257,6 +11876,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. invoices: type: array @@ -11423,6 +12076,7 @@ paths: enum: - sandbox - live + type: string description: The environment (sandbox/live) subscriptions: type: array @@ -11450,6 +12104,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -11573,6 +12228,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -11658,6 +12314,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. invoices: type: array @@ -11800,6 +12490,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls to replace on the entity. required: - entity_id @@ -11849,6 +12573,7 @@ paths: enum: - sandbox - live + type: string description: The environment (sandbox/live) subscriptions: type: array @@ -11876,6 +12601,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -11999,6 +12725,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -12084,6 +12811,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. invoices: type: array @@ -12360,3 +13121,317 @@ x-speakeasy-globals: type: string default: 2.2.0 x-speakeasy-globals-hidden: true +webhooks: + balances.usage_alert_triggered: + post: + operationId: balancesUsageAlertTriggered + summary: Usage Alert Triggered + description: Fired when a customer crosses a configured usage alert threshold. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: balances.usage_alert_triggered + description: The webhook event type. + data: + examples: + - customer_id: org_123 + feature_id: api_calls + entity_id: workspace_abc + usage_alert: + name: 80% usage warning + threshold: 80 + threshold_type: usage_percentage_threshold + type: object + properties: + customer_id: + description: The ID of the customer whose usage alert was triggered. + type: string + feature_id: + description: The feature ID the alert applies to. + type: string + entity_id: + description: The entity ID the alert applies to, if the usage was entity-scoped. + type: string + usage_alert: + description: Details of the usage alert that was triggered. + type: object + properties: + name: + description: User-defined label for the alert, if provided. + type: string + threshold: + description: The threshold value that was crossed. + type: number + threshold_type: + description: Whether the threshold is an absolute usage count or a percentage. + type: string + enum: + - usage + - usage_percentage + required: + - threshold + - threshold_type + additionalProperties: false + required: + - customer_id + - feature_id + - usage_alert + additionalProperties: false + example: + type: balances.usage_alert_triggered + data: + customer_id: org_123 + feature_id: api_calls + entity_id: workspace_abc + usage_alert: + name: 80% usage warning + threshold: 80 + threshold_type: usage_percentage_threshold + responses: + "200": + description: Webhook received successfully. + balances.limit_reached: + post: + operationId: balancesLimitReached + summary: Limit Reached + description: Fired when a customer reaches the limit for a feature (included + allowance, max purchase, or spend limit). + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: balances.limit_reached + description: The webhook event type. + data: + examples: + - customer_id: org_123 + entity_id: workspace_abc + feature_id: api_calls + limit_type: included + type: object + properties: + customer_id: + description: The ID of the customer who hit the limit. + type: string + entity_id: + description: The entity ID, if the limit was reached on a specific entity. + type: string + feature_id: + description: The feature ID whose limit was reached. + type: string + limit_type: + description: "Which limit was hit: included allowance, max purchase cap, or + spend limit." + type: string + enum: + - included + - max_purchase + - spend_limit + required: + - customer_id + - feature_id + - limit_type + additionalProperties: false + example: + type: balances.limit_reached + data: + customer_id: org_123 + entity_id: workspace_abc + feature_id: api_calls + limit_type: included + responses: + "200": + description: Webhook received successfully. + vercel.resources.deleted: + post: + operationId: vercelResourcesDeleted + summary: Resource Deleted + description: When a Vercel resource is deleted, you'll need to handle + de-provisioning any API keys or other non-Autumn controlled data for + this user. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.resources.deleted + description: The webhook event type. + data: + type: object + properties: + resource: + description: The resource that was deleted. + type: object + properties: + id: + description: The unique identifier of the deleted resource. + type: string + required: + - id + additionalProperties: false + installation_id: + description: The Vercel integration configuration ID. + type: string + required: + - resource + - installation_id + additionalProperties: false + responses: + "200": + description: Webhook received successfully. + vercel.resources.provisioned: + post: + operationId: vercelResourcesProvisioned + summary: Resource Provisioned + description: When a Vercel resource is created, you'll need to provision a + secret key for your service. Then you can use the provided access token + to patch the resource's secrets. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.resources.provisioned + description: The webhook event type. + data: + type: object + properties: + resource: + description: The resource that was provisioned. + type: object + properties: + id: + description: The unique identifier of the provisioned resource. + type: string + name: + description: The display name of the provisioned resource. + type: string + required: + - id + - name + additionalProperties: false + installation_id: + description: The Vercel integration configuration ID. + type: string + access_token: + description: An access token that can be used to patch the resource's secrets. + type: string + required: + - resource + - installation_id + - access_token + additionalProperties: false + responses: + "200": + description: Webhook received successfully. + vercel.resources.rotate_secrets: + post: + operationId: vercelResourcesRotateSecrets + summary: Rotate Secrets + description: This event is sent when Vercel requires a resource's secrets to be + rotated. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.resources.rotate_secrets + description: The webhook event type. + data: + type: object + properties: + resource: + description: The resource whose secrets should be rotated. + type: object + properties: + id: + description: The unique identifier of the resource. + type: string + required: + - id + additionalProperties: false + installation_id: + description: The Vercel integration configuration ID. + type: string + vercel_request_body: + description: The raw request body from Vercel's rotation request. + required: + - resource + - installation_id + - vercel_request_body + additionalProperties: false + responses: + "200": + description: Webhook received successfully. + vercel.webhooks.event: + post: + operationId: vercelWebhooksEvent + summary: Webhook Event + description: Passthrough webhook for Vercel events. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.webhooks.event + description: The webhook event type. + data: + type: object + properties: + installation_id: + description: The Vercel integration configuration ID. + type: string + event: + description: The raw Vercel webhook event payload. + required: + - installation_id + - event + additionalProperties: false + responses: + "200": + description: Webhook received successfully. diff --git a/packages/openapi/openapi.yml b/packages/openapi/openapi.yml index a100860e5..3ffd44946 100644 --- a/packages/openapi/openapi.yml +++ b/packages/openapi/openapi.yml @@ -86,6 +86,7 @@ components: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -122,6 +123,40 @@ components: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a percentage + (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) title: CustomerData description: Customer details to set when creating a customer @@ -137,6 +172,7 @@ components: - purchases.plan - balances.feature - flags.feature + type: string title: CustomerExpand Customer: type: object @@ -174,6 +210,7 @@ components: enum: - sandbox - live + type: string description: The environment this customer was created in. metadata: type: object @@ -216,6 +253,7 @@ components: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -252,6 +290,40 @@ components: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a percentage + (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) subscriptions: type: array @@ -279,6 +351,7 @@ components: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -406,6 +479,7 @@ components: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -543,6 +617,7 @@ components: enum: - sandbox - live + type: string description: The environment (sandbox/live) required: - id @@ -588,6 +663,7 @@ components: - fixed_discount - free_product - invoice_credits + type: string description: The type of reward discount_value: type: number @@ -597,6 +673,7 @@ components: - one_off - months - forever + type: string description: How long the discount lasts duration_value: anyOf: @@ -794,6 +871,7 @@ components: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -843,6 +921,7 @@ components: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -907,6 +986,7 @@ components: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. @@ -940,6 +1020,7 @@ components: enum: - graduated - volume + type: string interval: enum: - one_off @@ -948,6 +1029,7 @@ components: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -962,6 +1044,7 @@ components: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." max_purchase: @@ -1004,6 +1087,7 @@ components: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -1032,6 +1116,7 @@ components: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -1050,6 +1135,7 @@ components: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -1072,6 +1158,7 @@ components: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -1088,6 +1175,7 @@ components: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -1127,6 +1215,7 @@ components: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -1256,6 +1345,7 @@ components: - quarter - semi_annual - year + type: string - const: multiple description: The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals. @@ -1290,6 +1380,7 @@ components: enum: - graduated - volume + type: string description: "How tiers are applied: graduated (split across bands) or volume (flat rate for the matched tier)." billing_units: @@ -1299,6 +1390,7 @@ components: enum: - prepaid - usage_based + type: string description: Whether usage is prepaid or billed pay-per-use. max_purchase: anyOf: @@ -1517,6 +1609,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -1553,6 +1646,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) expand: type: array @@ -1629,6 +1756,7 @@ paths: enum: - active - scheduled + type: string description: Filter by customer product status. Defaults to active and scheduled search: type: string @@ -1684,6 +1812,7 @@ paths: enum: - sandbox - live + type: string description: The environment this customer was created in. metadata: type: object @@ -1726,6 +1855,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -1762,6 +1892,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this + is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) subscriptions: type: array @@ -1789,6 +1953,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -1917,6 +2082,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -2219,6 +2385,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -2255,6 +2422,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) new_customer_id: $ref: "#/components/schemas/CustomerId" @@ -2307,6 +2508,7 @@ paths: enum: - sandbox - live + type: string description: The environment this customer was created in. metadata: type: object @@ -2349,6 +2551,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -2385,6 +2588,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) subscriptions: type: array @@ -2412,6 +2649,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -2540,6 +2778,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -2857,6 +3096,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -2895,6 +3135,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -2931,6 +3172,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -2939,6 +3181,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -2954,6 +3197,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -2973,6 +3217,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -2981,6 +3226,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -2997,6 +3243,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -3023,6 +3270,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -3127,6 +3375,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -3176,6 +3425,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -3240,6 +3490,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. @@ -3273,6 +3524,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -3281,6 +3533,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -3296,6 +3549,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." @@ -3339,6 +3593,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -3367,6 +3622,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -3385,6 +3641,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -3407,6 +3664,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -3423,6 +3681,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -3608,6 +3867,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -3657,6 +3917,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -3721,6 +3982,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. @@ -3754,6 +4016,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -3762,6 +4025,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -3777,6 +4041,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." @@ -3820,6 +4085,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -3848,6 +4114,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -3866,6 +4133,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -3888,6 +4156,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -3904,6 +4173,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -4067,6 +4337,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -4116,6 +4387,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -4180,6 +4452,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units @@ -4214,6 +4487,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -4222,6 +4496,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -4237,6 +4512,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." @@ -4281,6 +4557,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -4310,6 +4587,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -4328,6 +4606,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -4350,6 +4629,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -4366,6 +4646,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -4533,6 +4814,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -4573,6 +4855,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -4609,6 +4892,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -4617,6 +4901,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -4632,6 +4917,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -4651,6 +4937,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -4659,6 +4946,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -4675,6 +4963,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -4702,6 +4991,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -4791,6 +5081,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -4840,6 +5131,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -4904,6 +5196,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. @@ -4937,6 +5230,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -4945,6 +5239,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: @@ -4960,6 +5255,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." @@ -5003,6 +5299,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -5031,6 +5328,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -5049,6 +5347,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -5071,6 +5370,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: @@ -5087,6 +5387,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: @@ -5316,6 +5617,7 @@ paths: - boolean - metered - credit_system + type: string description: The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. @@ -5399,6 +5701,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -5519,6 +5822,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -5624,6 +5928,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -5789,6 +6094,7 @@ paths: - boolean - metered - credit_system + type: string description: The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. @@ -5872,6 +6178,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -6178,6 +6485,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -6219,6 +6527,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -6255,6 +6564,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -6263,6 +6573,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -6278,6 +6589,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -6297,6 +6609,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -6305,6 +6618,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -6321,6 +6635,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -6347,6 +6662,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -6390,6 +6706,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -6398,6 +6715,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -6434,6 +6752,7 @@ paths: enum: - immediate - end_of_cycle + type: string description: When the plan change should take effect. 'immediate' applies now, 'end_of_cycle' schedules for the end of the current billing cycle. By default, upgrades are immediate and downgrades are @@ -6563,6 +6882,7 @@ paths: - 3ds_required - payment_method_required - payment_failed + type: string description: The type of action required to complete the payment. reason: type: string @@ -6661,6 +6981,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -6702,6 +7023,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -6738,6 +7060,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -6746,6 +7069,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -6762,6 +7086,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -6781,6 +7106,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -6789,6 +7115,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -6805,6 +7132,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -6863,6 +7191,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -6929,6 +7258,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -6969,6 +7299,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is + a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. required: - feature_id @@ -7043,6 +7407,7 @@ paths: - 3ds_required - payment_method_required - payment_failed + type: string description: The type of action required to complete the payment. reason: type: string @@ -7220,6 +7585,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -7261,6 +7627,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -7297,6 +7664,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -7305,6 +7673,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -7320,6 +7689,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -7339,6 +7709,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -7347,6 +7718,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -7363,6 +7735,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -7389,6 +7762,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -7432,6 +7806,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -7440,6 +7815,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -7476,6 +7852,7 @@ paths: enum: - immediate - end_of_cycle + type: string description: When the plan change should take effect. 'immediate' applies now, 'end_of_cycle' schedules for the end of the current billing cycle. By default, upgrades are immediate and downgrades are @@ -7799,10 +8176,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. - required: + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. + required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being added or updated. outgoing: type: array @@ -7836,10 +8227,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being removed or ended. required: - customer_id @@ -7931,6 +8336,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -7972,6 +8378,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -8008,6 +8415,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -8016,6 +8424,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -8032,6 +8441,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -8051,6 +8461,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -8059,6 +8470,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -8075,6 +8487,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -8133,6 +8546,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -8199,6 +8613,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -8239,6 +8654,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is + a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. required: - feature_id @@ -8507,10 +8956,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being added or updated. outgoing: type: array @@ -8544,10 +9007,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being removed or ended. required: - customer_id @@ -8662,6 +9139,9 @@ paths: @param noBillingChanges - If true, the subscription is updated internally without applying billing changes in Stripe. (optional) + @param recalculateBalances - Controls whether balances should be + recalculated during the subscription update. (optional) + @returns A billing response with customer ID, invoice details, and payment URL (if next action is required). @@ -8728,6 +9208,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -8769,6 +9250,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -8805,6 +9287,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -8813,6 +9296,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -8828,6 +9312,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -8847,6 +9332,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -8855,6 +9341,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -8871,6 +9358,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -8897,6 +9385,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -8940,6 +9429,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -8948,6 +9438,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -8962,6 +9453,7 @@ paths: - cancel_immediately - cancel_end_of_cycle - uncancel + type: string description: Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. @@ -8969,6 +9461,17 @@ paths: type: boolean description: If true, the subscription is updated internally without applying billing changes in Stripe. + recalculate_balances: + type: object + properties: + enabled: + type: boolean + description: If true, recalculates balances during the subscription update. Only + applicable when updating feature quantities. + required: + - enabled + description: Controls whether balances should be recalculated during the + subscription update. required: - customer_id title: UpdateSubscriptionParams @@ -9037,6 +9540,7 @@ paths: - 3ds_required - payment_method_required - payment_failed + type: string description: The type of action required to complete the payment. reason: type: string @@ -9128,6 +9632,9 @@ paths: @param noBillingChanges - If true, the subscription is updated internally without applying billing changes in Stripe. (optional) + @param recalculateBalances - Controls whether balances should be + recalculated during the subscription update. (optional) + @returns A preview response with line items showing prorated charges or credits for the proposed changes. @@ -9194,6 +9701,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -9235,6 +9743,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -9271,6 +9780,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -9279,6 +9789,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -9294,6 +9805,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -9313,6 +9825,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -9321,6 +9834,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -9337,6 +9851,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -9363,6 +9878,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -9406,6 +9922,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -9414,6 +9931,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. @@ -9428,6 +9946,7 @@ paths: - cancel_immediately - cancel_end_of_cycle - uncancel + type: string description: Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. @@ -9435,6 +9954,17 @@ paths: type: boolean description: If true, the subscription is updated internally without applying billing changes in Stripe. + recalculate_balances: + type: object + properties: + enabled: + type: boolean + description: If true, recalculates balances during the subscription update. Only + applicable when updating feature quantities. + required: + - enabled + description: Controls whether balances should be recalculated during the + subscription update. required: - customer_id title: PreviewUpdateParams @@ -9697,10 +10227,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being added or updated. outgoing: type: array @@ -9734,10 +10278,24 @@ paths: - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, + or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or + null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being removed or ended. intent: enum: @@ -9747,6 +10305,7 @@ paths: - cancel_end_of_cycle - uncancel - none + type: string required: - customer_id - line_items @@ -9892,6 +10451,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -9933,6 +10493,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: @@ -9969,6 +10530,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -9977,6 +10539,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: @@ -9992,6 +10555,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: @@ -10011,6 +10575,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -10019,6 +10584,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -10035,6 +10601,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -10061,6 +10628,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -10081,6 +10649,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. @@ -10245,6 +10814,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the balance resets (e.g., 'month', 'day', 'year'). interval_count: @@ -10335,6 +10905,7 @@ paths: - quarter - semi_annual - year + type: string description: Target a specific balance by its reset interval. Use when the customer has multiple balances for the same feature with different reset intervals. @@ -10411,6 +10982,7 @@ paths: - quarter - semi_annual - year + type: string description: Target a specific balance by its reset interval. Use when the customer has multiple balances for the same feature with different reset intervals. @@ -10456,6 +11028,7 @@ paths: enum: - confirm - release + type: string description: Use 'confirm' to commit the deduction, or 'release' to return the held balance. override_value: @@ -10695,6 +11268,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -10769,6 +11343,7 @@ paths: enum: - usage_limit - feature_flag + type: string description: The reason access was denied. 'usage_limit' means the customer exceeded their balance, 'feature_flag' means the feature is not included in their plan. @@ -10804,6 +11379,7 @@ paths: enum: - sandbox - live + type: string description: The environment of the product is_add_on: type: boolean @@ -10833,6 +11409,7 @@ paths: - feature - priced_feature - price + type: string - type: "null" description: The type of the product item feature_id: @@ -10848,6 +11425,7 @@ paths: - continuous_use - boolean - static + type: string - type: "null" description: Single use features are used once and then depleted, like API calls or credits. Continuous use features are @@ -10871,6 +11449,7 @@ paths: - quarter - semi_annual - year + type: string - type: "null" description: The reset or billing interval of the product item. If null, feature will have no reset date, and if there's a @@ -10901,6 +11480,7 @@ paths: - enum: - graduated - volume + type: string - type: "null" description: "How tiers are applied: graduated (split across bands) or volume (flat rate for the matched tier). Defaults @@ -10910,6 +11490,7 @@ paths: - enum: - prepaid - pay_per_use + type: string - type: "null" description: Whether the feature should be prepaid upfront or billed for how much they use end of billing period. @@ -10970,6 +11551,7 @@ paths: enum: - month - forever + type: string default: month length: type: number @@ -10984,6 +11566,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string - type: "null" on_decrease: anyOf: @@ -10993,6 +11576,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string - type: "null" - type: "null" description: Configuration for rollover and proration behavior of the feature. @@ -11008,6 +11592,7 @@ paths: - day - month - year + type: string description: The duration type of the free trial length: type: number @@ -11052,6 +11637,7 @@ paths: - cancel - expired - past_due + type: string description: Scenario for when this product is used in attach flows properties: type: object @@ -11506,6 +12092,7 @@ paths: - last_cycle - 1bc - 3bc + type: string description: Time range to aggregate events for. Either range or custom_range must be provided bin_size: @@ -11513,6 +12100,7 @@ paths: - day - hour - month + type: string default: day description: Size of the time bins to aggregate events for. Defaults to hour if range is 24h, otherwise day @@ -11733,6 +12321,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. customer_data: $ref: "#/components/schemas/CustomerData" @@ -11789,6 +12411,7 @@ paths: enum: - sandbox - live + type: string description: The environment (sandbox/live) subscriptions: type: array @@ -11816,6 +12439,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -11939,6 +12563,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -12024,6 +12649,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. invoices: type: array @@ -12219,6 +12878,7 @@ paths: enum: - sandbox - live + type: string description: The environment (sandbox/live) subscriptions: type: array @@ -12246,6 +12906,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -12369,6 +13030,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -12454,6 +13116,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. invoices: type: array @@ -12604,6 +13300,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls to replace on the entity. required: - entity_id @@ -12651,6 +13381,7 @@ paths: enum: - sandbox - live + type: string description: The environment (sandbox/live) subscriptions: type: array @@ -12678,6 +13409,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -12801,6 +13533,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." @@ -12886,6 +13619,40 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies + globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an + absolute count. For usage_percentage, this is a + percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of + the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the + same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. invoices: type: array @@ -13167,3 +13934,317 @@ x-speakeasy-globals: type: string default: 2.2.0 x-speakeasy-globals-hidden: true +webhooks: + balances.usage_alert_triggered: + post: + operationId: balancesUsageAlertTriggered + summary: Usage Alert Triggered + description: Fired when a customer crosses a configured usage alert threshold. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: balances.usage_alert_triggered + description: The webhook event type. + data: + examples: + - customer_id: org_123 + feature_id: api_calls + entity_id: workspace_abc + usage_alert: + name: 80% usage warning + threshold: 80 + threshold_type: usage_percentage_threshold + type: object + properties: + customer_id: + description: The ID of the customer whose usage alert was triggered. + type: string + feature_id: + description: The feature ID the alert applies to. + type: string + entity_id: + description: The entity ID the alert applies to, if the usage was entity-scoped. + type: string + usage_alert: + description: Details of the usage alert that was triggered. + type: object + properties: + name: + description: User-defined label for the alert, if provided. + type: string + threshold: + description: The threshold value that was crossed. + type: number + threshold_type: + description: Whether the threshold is an absolute usage count or a percentage. + type: string + enum: + - usage + - usage_percentage + required: + - threshold + - threshold_type + additionalProperties: false + required: + - customer_id + - feature_id + - usage_alert + additionalProperties: false + example: + type: balances.usage_alert_triggered + data: + customer_id: org_123 + feature_id: api_calls + entity_id: workspace_abc + usage_alert: + name: 80% usage warning + threshold: 80 + threshold_type: usage_percentage_threshold + responses: + "200": + description: Webhook received successfully. + balances.limit_reached: + post: + operationId: balancesLimitReached + summary: Limit Reached + description: Fired when a customer reaches the limit for a feature (included + allowance, max purchase, or spend limit). + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: balances.limit_reached + description: The webhook event type. + data: + examples: + - customer_id: org_123 + entity_id: workspace_abc + feature_id: api_calls + limit_type: included + type: object + properties: + customer_id: + description: The ID of the customer who hit the limit. + type: string + entity_id: + description: The entity ID, if the limit was reached on a specific entity. + type: string + feature_id: + description: The feature ID whose limit was reached. + type: string + limit_type: + description: "Which limit was hit: included allowance, max purchase cap, or + spend limit." + type: string + enum: + - included + - max_purchase + - spend_limit + required: + - customer_id + - feature_id + - limit_type + additionalProperties: false + example: + type: balances.limit_reached + data: + customer_id: org_123 + entity_id: workspace_abc + feature_id: api_calls + limit_type: included + responses: + "200": + description: Webhook received successfully. + vercel.resources.deleted: + post: + operationId: vercelResourcesDeleted + summary: Resource Deleted + description: When a Vercel resource is deleted, you'll need to handle + de-provisioning any API keys or other non-Autumn controlled data for + this user. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.resources.deleted + description: The webhook event type. + data: + type: object + properties: + resource: + description: The resource that was deleted. + type: object + properties: + id: + description: The unique identifier of the deleted resource. + type: string + required: + - id + additionalProperties: false + installation_id: + description: The Vercel integration configuration ID. + type: string + required: + - resource + - installation_id + additionalProperties: false + responses: + "200": + description: Webhook received successfully. + vercel.resources.provisioned: + post: + operationId: vercelResourcesProvisioned + summary: Resource Provisioned + description: When a Vercel resource is created, you'll need to provision a + secret key for your service. Then you can use the provided access token + to patch the resource's secrets. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.resources.provisioned + description: The webhook event type. + data: + type: object + properties: + resource: + description: The resource that was provisioned. + type: object + properties: + id: + description: The unique identifier of the provisioned resource. + type: string + name: + description: The display name of the provisioned resource. + type: string + required: + - id + - name + additionalProperties: false + installation_id: + description: The Vercel integration configuration ID. + type: string + access_token: + description: An access token that can be used to patch the resource's secrets. + type: string + required: + - resource + - installation_id + - access_token + additionalProperties: false + responses: + "200": + description: Webhook received successfully. + vercel.resources.rotate_secrets: + post: + operationId: vercelResourcesRotateSecrets + summary: Rotate Secrets + description: This event is sent when Vercel requires a resource's secrets to be + rotated. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.resources.rotate_secrets + description: The webhook event type. + data: + type: object + properties: + resource: + description: The resource whose secrets should be rotated. + type: object + properties: + id: + description: The unique identifier of the resource. + type: string + required: + - id + additionalProperties: false + installation_id: + description: The Vercel integration configuration ID. + type: string + vercel_request_body: + description: The raw request body from Vercel's rotation request. + required: + - resource + - installation_id + - vercel_request_body + additionalProperties: false + responses: + "200": + description: Webhook received successfully. + vercel.webhooks.event: + post: + operationId: vercelWebhooksEvent + summary: Webhook Event + description: Passthrough webhook for Vercel events. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.webhooks.event + description: The webhook event type. + data: + type: object + properties: + installation_id: + description: The Vercel integration configuration ID. + type: string + event: + description: The raw Vercel webhook event payload. + required: + - installation_id + - event + additionalProperties: false + responses: + "200": + description: Webhook received successfully. diff --git a/packages/openapi/package.json b/packages/openapi/package.json index 3cbdfeba8..29aaf1b7f 100644 --- a/packages/openapi/package.json +++ b/packages/openapi/package.json @@ -12,6 +12,8 @@ "@orpc/contract": "catalog:", "@orpc/openapi": "^1.13.4", "@orpc/zod": "^1.13.4", + "dotenv": "^17.3.1", + "svix": "^1.89.0", "yaml": "^2.8.1", "zod-openapi": "^5.4.1" }, diff --git a/packages/openapi/scripts/svixPush.ts b/packages/openapi/scripts/svixPush.ts new file mode 100644 index 000000000..ffb9ab029 --- /dev/null +++ b/packages/openapi/scripts/svixPush.ts @@ -0,0 +1,102 @@ +import { webhookRegistry } from "@autumn/shared"; +import { config } from "dotenv"; +import { Svix } from "svix"; +import { createSchema } from "zod-openapi"; + +config({ path: "server/.env" }); + +const SVIX_API_KEY = process.env.SVIX_API_KEY; +if (!SVIX_API_KEY) { + console.error("SVIX_API_KEY is not set. Provide it via env or server/.env"); + process.exit(1); +} + +const svix = new Svix(SVIX_API_KEY); + +async function pushEventTypes() { + let created = 0; + let updated = 0; + let failed = 0; + + for (const definition of webhookRegistry) { + const schemas = definition.schema + ? buildJsonSchemas({ definition }) + : undefined; + + const payload = { + name: definition.eventType, + description: definition.description, + schemas, + deprecated: definition.deprecated, + archived: definition.archived, + featureFlags: definition.featureFlags, + }; + + try { + await svix.eventType.create(payload); + console.log(` [created] ${definition.eventType}`); + created++; + } catch (error: unknown) { + if (isConflictError(error)) { + try { + const { name: _name, ...updatePayload } = payload; + await svix.eventType.update(definition.eventType, updatePayload); + console.log(` [updated] ${definition.eventType}`); + updated++; + } catch (updateError) { + console.error( + ` [failed] ${definition.eventType} — update failed:`, + updateError, + ); + failed++; + } + } else { + console.error( + ` [failed] ${definition.eventType} — create failed:`, + error, + ); + failed++; + } + } + } + + console.log( + `\nDone: ${created} created, ${updated} updated, ${failed} failed (${webhookRegistry.length} total)`, + ); + + if (failed > 0) process.exit(1); +} + +function buildJsonSchemas({ + definition, +}: { + definition: (typeof webhookRegistry)[number]; +}) { + if (!definition.schema) return undefined; + + const { schema: jsonSchema } = createSchema(definition.schema); + return { + 1: { + type: "object", + required: ["type", "data"], + properties: { + type: { + type: "string", + const: definition.eventType, + }, + data: jsonSchema, + }, + }, + }; +} + +function isConflictError(error: unknown): boolean { + if (!error || typeof error !== "object") return false; + const statusCode = + (error as Record).code ?? + (error as Record).statusCode ?? + (error as Record).status; + return statusCode === 409; +} + +pushEventTypes(); diff --git a/packages/openapi/utils/apiReferenceGenerator/generateFields.ts b/packages/openapi/utils/apiReferenceGenerator/generateFields.ts index b18c0046f..0c9b216a7 100644 --- a/packages/openapi/utils/apiReferenceGenerator/generateFields.ts +++ b/packages/openapi/utils/apiReferenceGenerator/generateFields.ts @@ -1,4 +1,8 @@ -import type { ParsedOperation, SchemaField } from "./parseOpenApi.js"; +import type { + ParsedOperation, + ParsedWebhook, + SchemaField, +} from "./parseOpenApi.js"; /** * Generate the MDX content for request body and response fields. @@ -233,6 +237,79 @@ function formatType(type: string, enumValues?: string[]): string { return type; } +/** + * Generate MDX content for a webhook event. + * Uses plain `ParamField` with snake_case names since webhook payloads + * are raw JSON (not SDK-transformed). + */ +export function generateWebhookFields({ + webhook, +}: { + webhook: ParsedWebhook; +}): string { + const sections: string[] = []; + + if (webhook.dataFields && webhook.dataFields.length > 0) { + sections.push("### Payload Fields\n"); + sections.push( + generateStaticParamFields({ fields: webhook.dataFields, indent: 0 }), + ); + } + + return sections.join("\n"); +} + +/** + * Generate ParamField components with static snake_case names. + */ +function generateStaticParamFields({ + fields, + indent, +}: { + fields: SchemaField[]; + indent: number; +}): string { + const indentStr = " ".repeat(indent); + const lines: string[] = []; + + for (const field of fields) { + const typeStr = formatType(field.type, field.enumValues); + const requiredAttr = field.required ? " required" : ""; + const description = escapeDescription(field.description); + const hasChildren = field.children && field.children.length > 0; + + if (hasChildren) { + lines.push( + `${indentStr}`, + ); + if (description) { + lines.push(`${indentStr} ${description}`); + } + lines.push(`${indentStr} `); + lines.push( + generateStaticParamFields({ + fields: field.children!, + indent: indent + 2, + }), + ); + lines.push(`${indentStr} `); + lines.push(`${indentStr}\n`); + } else if (description) { + lines.push( + `${indentStr}`, + ); + lines.push(`${indentStr} ${description}`); + lines.push(`${indentStr}\n`); + } else { + lines.push( + `${indentStr}\n`, + ); + } + } + + return lines.join("\n"); +} + /** * Escape special characters in description for MDX. */ diff --git a/packages/openapi/utils/apiReferenceGenerator/index.ts b/packages/openapi/utils/apiReferenceGenerator/index.ts index 5fe46b4da..afc5d8d1f 100644 --- a/packages/openapi/utils/apiReferenceGenerator/index.ts +++ b/packages/openapi/utils/apiReferenceGenerator/index.ts @@ -1,8 +1,9 @@ -import { existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { mkdirSync, writeFileSync } from "node:fs"; import path from "node:path"; -import { generateFields } from "./generateFields.js"; +import { webhookRegistry } from "@autumn/shared"; +import { generateFields, generateWebhookFields } from "./generateFields.js"; import { mergeMdx } from "./mergeMdx.js"; -import { parseOpenApi } from "./parseOpenApi.js"; +import { parseOpenApi, parseWebhooks } from "./parseOpenApi.js"; export interface GenerateApiReferenceOptions { openApiPath: string; @@ -10,6 +11,11 @@ export interface GenerateApiReferenceOptions { outputDir: string; } +export interface GeneratedWebhookPage { + group: string; + pagePath: string; +} + /** * Generate API reference MDX files from an OpenAPI spec. * @@ -18,51 +24,129 @@ export interface GenerateApiReferenceOptions { * 2. Generate DynamicParamField/DynamicResponseField components * 3. Merge with manual MDX content (if exists) * 4. Write to output directory: {outputDir}/{tag}/{operationId}.mdx + * + * Also generates webhook MDX files from the `webhooks` section + * and returns group metadata so callers can update navigation. */ export async function generateApiReference({ openApiPath, manualMdxDir, outputDir, -}: GenerateApiReferenceOptions): Promise { +}: GenerateApiReferenceOptions): Promise<{ + webhookPages: GeneratedWebhookPage[]; +}> { console.log(` Reading OpenAPI spec from: ${openApiPath}`); - // Parse OpenAPI spec const operations = parseOpenApi({ openApiPath }); console.log(` Found ${operations.length} operations`); let generated = 0; - const skipped = 0; for (const operation of operations) { const { tag, operationId } = operation; - // Determine file paths const manualMdxPath = path.join(manualMdxDir, tag, `${operationId}.mdx`); const outputPath = path.join(outputDir, tag, `${operationId}.mdx`); - // Generate fields MDX const generatedContent = generateFields({ operation }); - // Merge with manual MDX (if exists) const finalMdx = mergeMdx({ manualMdxPath, generatedContent, operation, }); - // Ensure output directory exists mkdirSync(path.dirname(outputPath), { recursive: true }); - - // Write output file writeFileSync(outputPath, finalMdx, "utf-8"); generated++; console.log(` Generated: ${tag}/${operationId}.mdx`); } - console.log( - ` API reference generation complete: ${generated} generated, ${skipped} skipped`, - ); + // Build operationId -> group mapping from the shared registry + const groupMap: Record = {}; + for (const definition of webhookRegistry) { + groupMap[definition.operationId] = definition.group; + } + + const webhooks = parseWebhooks({ openApiPath, groupMap }); + const webhookPages: GeneratedWebhookPage[] = []; + + if (webhooks.length > 0) { + console.log(` Found ${webhooks.length} webhooks`); + + for (const webhook of webhooks) { + const generatedContent = generateWebhookFields({ webhook }); + + const title = + webhook.summary ?? formatEventTypeAsTitle(webhook.eventType); + const frontmatter = `---\ntitle: "${title}"\nopenapi: "api/openapi.yml webhook ${webhook.eventType}"\n---`; + + const finalMdx = `${frontmatter}\n\n${generatedContent}`; + + const relativePage = `api-reference/webhooks/${webhook.operationId}`; + const outputPath = path.join( + outputDir, + "webhooks", + `${webhook.operationId}.mdx`, + ); + mkdirSync(path.dirname(outputPath), { recursive: true }); + writeFileSync(outputPath, finalMdx, "utf-8"); + generated++; + + webhookPages.push({ + group: webhook.group, + pagePath: relativePage, + }); + + console.log(` Generated webhook: webhooks/${webhook.operationId}.mdx`); + } + } + + // Also generate placeholder pages for registry entries without schemas + // (they won't be in the OpenAPI spec but we still want a docs page) + const generatedOperationIds = new Set(webhooks.map((w) => w.operationId)); + for (const definition of webhookRegistry) { + if (generatedOperationIds.has(definition.operationId)) continue; + + const title = definition.title; + const mdx = `---\ntitle: "${title}"\n---\n\n${definition.description}\n\nSchema documentation for this event type is coming soon.\n`; + + const relativePage = `api-reference/webhooks/${definition.operationId}`; + const outputPath = path.join( + outputDir, + "webhooks", + `${definition.operationId}.mdx`, + ); + mkdirSync(path.dirname(outputPath), { recursive: true }); + writeFileSync(outputPath, mdx, "utf-8"); + generated++; + + webhookPages.push({ + group: definition.group, + pagePath: relativePage, + }); + + console.log( + ` Generated webhook placeholder: webhooks/${definition.operationId}.mdx`, + ); + } + + console.log(` API reference generation complete: ${generated} generated`); + + return { webhookPages }; +} + +function formatEventTypeAsTitle(eventType: string): string { + return eventType + .split(".") + .map((segment) => + segment + .split("_") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "), + ) + .join(" — "); } // Re-export types for consumers diff --git a/packages/openapi/utils/apiReferenceGenerator/parseOpenApi.ts b/packages/openapi/utils/apiReferenceGenerator/parseOpenApi.ts index f511b1913..fbf168b11 100644 --- a/packages/openapi/utils/apiReferenceGenerator/parseOpenApi.ts +++ b/packages/openapi/utils/apiReferenceGenerator/parseOpenApi.ts @@ -33,11 +33,22 @@ export interface ParsedOperation { allSchemas?: Record; } +export interface ParsedWebhook { + eventType: string; + operationId: string; + group: string; + summary?: string; + description?: string; + /** Fields inside the `data` envelope (excludes the outer `type` field) */ + dataFields?: SchemaField[]; +} + interface OpenApiDocument { components?: { schemas?: Record; }; paths?: Record>; + webhooks?: Record>; } /** @@ -151,6 +162,74 @@ export function parseOpenApi({ return operations; } +/** + * Parse webhook definitions from the OpenAPI `webhooks` section. + * Extracts the `data` sub-schema from the `{ type, data }` envelope + * so we can generate field docs for just the meaningful payload. + * + * @param groupMap - Maps operationId -> group name (from the webhook registry). + */ +export function parseWebhooks({ + openApiPath, + groupMap = {}, +}: { + openApiPath: string; + groupMap?: Record; +}): ParsedWebhook[] { + const content = readFileSync(openApiPath, "utf-8"); + const doc = yaml.parse(content) as OpenApiDocument; + + const schemas = doc.components?.schemas ?? {}; + const webhooks: ParsedWebhook[] = []; + + for (const [eventType, webhookItem] of Object.entries(doc.webhooks ?? {})) { + const postOp = webhookItem.post as Record | undefined; + if (!postOp) continue; + + const operationId = postOp.operationId as string | undefined; + if (!operationId) continue; + + const parsed: ParsedWebhook = { + eventType, + operationId, + group: groupMap[operationId] ?? "Webhooks", + summary: postOp.summary as string | undefined, + description: postOp.description as string | undefined, + }; + + const requestBody = postOp.requestBody as + | Record + | undefined; + const jsonContent = ( + requestBody?.content as Record | undefined + )?.["application/json"] as Record | undefined; + const bodySchema = jsonContent?.schema as + | Record + | undefined; + + if (bodySchema) { + const properties = bodySchema.properties as + | Record + | undefined; + const dataSchema = properties?.data as + | Record + | undefined; + + if (dataSchema) { + parsed.dataFields = parseSchema({ + schema: dataSchema, + schemas, + requiredFields: (dataSchema.required as string[]) ?? [], + }); + } + } + + webhooks.push(parsed); + } + + return webhooks; +} + /** * Resolves an example from a schema, following $ref if needed. */ diff --git a/packages/openapi/utils/mintlifyTransform/index.ts b/packages/openapi/utils/mintlifyTransform/index.ts index f74947dbb..3175c7c1f 100644 --- a/packages/openapi/utils/mintlifyTransform/index.ts +++ b/packages/openapi/utils/mintlifyTransform/index.ts @@ -1,7 +1,10 @@ import { readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; import yaml from "yaml"; -import { generateApiReference } from "../apiReferenceGenerator/index.js"; +import { + type GeneratedWebhookPage, + generateApiReference, +} from "../apiReferenceGenerator/index.js"; import { removeInternalFields } from "../openapiTransform/removeInternalFields.js"; import { transformNode } from "./transformNode.js"; @@ -26,7 +29,6 @@ export function transformOpenApiForMintlify(yamlContent: string): string { | Record | undefined; - // Remove internal fields before other transformations removeInternalFields({ openApiDocument: doc }); transformNode(doc, schemas); @@ -38,6 +40,7 @@ export function transformOpenApiForMintlify(yamlContent: string): string { * * 1. Transforms OpenAPI (strips JSDoc tags, fixes code samples) * 2. Generates API reference MDX files with dynamic parameter fields + * 3. Updates docs.json navigation with webhook groups */ export async function generateMintlifyDocs({ openApiPath, @@ -46,21 +49,91 @@ export async function generateMintlifyDocs({ openApiPath: string; docsDir: string; }): Promise { - // Transform OpenAPI for Mintlify console.log("Transforming OpenAPI for Mintlify docs..."); const yamlContent = readFileSync(openApiPath, "utf-8"); const transformedYaml = transformOpenApiForMintlify(yamlContent); writeFileSync(openApiPath, transformedYaml); console.log("Mintlify transformation complete"); - // Generate API reference MDX files console.log("Generating API reference MDX files..."); const manualMdxDir = path.resolve(docsDir, "../api-reference-generator"); const outputMdxDir = path.resolve(docsDir, "api-reference"); - await generateApiReference({ + const { webhookPages } = await generateApiReference({ openApiPath, manualMdxDir, outputDir: outputMdxDir, }); console.log("API reference MDX generation complete"); + + if (webhookPages.length > 0) { + updateDocsJsonWebhooks({ docsDir, webhookPages }); + } +} + +/** + * Updates the docs.json navigation to include webhook groups in the + * API Reference tab, replacing any previously generated webhook groups. + */ +function updateDocsJsonWebhooks({ + docsDir, + webhookPages, +}: { + docsDir: string; + webhookPages: GeneratedWebhookPage[]; +}) { + const docsJsonPath = path.join(docsDir, "docs.json"); + const docsJson = JSON.parse(readFileSync(docsJsonPath, "utf-8")); + + const tabs = docsJson.navigation?.tabs; + if (!Array.isArray(tabs)) return; + + const apiTab = tabs.find( + (tab: Record) => tab.tab === "API Reference", + ); + if (!apiTab?.groups || !Array.isArray(apiTab.groups)) return; + + // Build nested subgroups under a single "Webhook Events" group + const groupedPages = new Map(); + for (const page of webhookPages) { + const existing = groupedPages.get(page.group) ?? []; + existing.push(page.pagePath); + groupedPages.set(page.group, existing); + } + + const subgroups = [...groupedPages.entries()].map( + ([groupName, pages]) => ({ + group: groupName, + pages, + }), + ); + + const webhookEventsGroup = { + group: "Webhook Events", + pages: subgroups, + }; + + // Remove any existing "Webhook Events" or old "Webhooks:" groups + const filteredGroups = apiTab.groups.filter( + (group: Record) => { + const name = group.group as string | undefined; + return name && !name.startsWith("Webhooks:") && name !== "Webhook Events"; + }, + ); + + // Insert before "Platform (Beta)" if it exists, otherwise at the end + const platformIdx = filteredGroups.findIndex( + (g: Record) => g.group === "Platform (Beta)", + ); + if (platformIdx >= 0) { + filteredGroups.splice(platformIdx, 0, webhookEventsGroup); + } else { + filteredGroups.push(webhookEventsGroup); + } + + apiTab.groups = filteredGroups; + + writeFileSync(docsJsonPath, `${JSON.stringify(docsJson, null, "\t")}\n`); + console.log( + ` Updated docs.json with ${subgroups.length} webhook subgroup(s)`, + ); } diff --git a/packages/openapi/v2.1/openapi2.1.ts b/packages/openapi/v2.1/openapi2.1.ts index c63ef850e..e4dd8229e 100644 --- a/packages/openapi/v2.1/openapi2.1.ts +++ b/packages/openapi/v2.1/openapi2.1.ts @@ -36,6 +36,7 @@ import { } from "../utils/openapiTransform/index.js"; import { registerInternalSchemas } from "../utils/registerInternalSchemas.js"; import { v2_1ContractRouter } from "./contracts/index.js"; +import { injectWebhooks } from "./webhooks/injectWebhooks.js"; const generator = new OpenAPIGenerator({ schemaConverters: [new ZodToJsonSchemaConverter()], @@ -120,6 +121,7 @@ async function generateOpenApiDocument(): Promise> { version: OPENAPI_DOC_VERSION, }); removeInternalFields({ openApiDocument }); + injectWebhooks({ openApiDocument }); return openApiDocument; } diff --git a/packages/openapi/v2.1/webhooks/injectWebhooks.ts b/packages/openapi/v2.1/webhooks/injectWebhooks.ts new file mode 100644 index 000000000..b15fbde63 --- /dev/null +++ b/packages/openapi/v2.1/webhooks/injectWebhooks.ts @@ -0,0 +1,81 @@ +import { createSchema } from "zod-openapi"; +import { z } from "zod/v4"; +import { webhookRegistry } from "./webhookDefinitions.js"; + +/** + * Injects OpenAPI `webhooks` entries and their component schemas into the + * generated OpenAPI document. Only definitions with a Zod schema are included; + * schema-less entries (description-only) are skipped. + */ +export function injectWebhooks({ + openApiDocument, +}: { + openApiDocument: Record; +}) { + const webhooks: Record = {}; + + const components = (openApiDocument.components ?? {}) as Record< + string, + unknown + >; + const existingSchemas = (components.schemas ?? {}) as Record; + + for (const definition of webhookRegistry) { + if (!definition.schema) continue; + + const { schema: jsonSchema, components: schemaComponents } = createSchema( + definition.schema, + ); + + for (const [name, schemaObj] of Object.entries(schemaComponents)) { + existingSchemas[name] = schemaObj; + } + + const meta = z.globalRegistry.get(definition.schema); + const schemaExample = meta?.examples?.[0] as + | Record + | undefined; + + webhooks[definition.eventType] = { + post: { + operationId: definition.operationId, + summary: definition.title, + description: definition.description, + requestBody: { + required: true, + content: { + "application/json": { + schema: { + type: "object", + required: ["type", "data"], + properties: { + type: { + type: "string", + const: definition.eventType, + description: "The webhook event type.", + }, + data: jsonSchema, + }, + }, + ...(schemaExample && { + example: { + type: definition.eventType, + data: schemaExample, + }, + }), + }, + }, + }, + responses: { + "200": { + description: "Webhook received successfully.", + }, + }, + }, + }; + } + + components.schemas = existingSchemas; + openApiDocument.components = components; + openApiDocument.webhooks = webhooks; +} diff --git a/packages/openapi/v2.1/webhooks/webhookDefinitions.ts b/packages/openapi/v2.1/webhooks/webhookDefinitions.ts new file mode 100644 index 000000000..37a66d094 --- /dev/null +++ b/packages/openapi/v2.1/webhooks/webhookDefinitions.ts @@ -0,0 +1 @@ +export { type WebhookDefinition, webhookRegistry } from "@autumn/shared"; diff --git a/packages/sdk/.npmignore b/packages/sdk/.npmignore index 670ac0a62..cf98a6bf0 100644 --- a/packages/sdk/.npmignore +++ b/packages/sdk/.npmignore @@ -5,14 +5,10 @@ !/**/*.ts !/**/*.js !/**/*.mjs -!/package.json -!/jsr.json -!/dist/**/*.json -!/esm/**/*.json +!/**/*.json !/**/*.map /eslint.config.mjs -/.oxlintrc.json /cjs /.tshy /.tshy-* diff --git a/packages/sdk/.speakeasy/gen.lock b/packages/sdk/.speakeasy/gen.lock index cfb8de934..dd20656ce 100644 --- a/packages/sdk/.speakeasy/gen.lock +++ b/packages/sdk/.speakeasy/gen.lock @@ -1,33 +1,33 @@ lockVersion: 2.0.0 id: 7b300647-cd76-49e9-bf77-7d1bf5446d66 management: - docChecksum: a9eda44de2ad5efffefc33432889b234 + docChecksum: a6df9b64f035c4af93b077b3174ecba8 docVersion: 2.2.0 - speakeasyVersion: 1.757.1 - generationVersion: 2.866.2 + speakeasyVersion: 1.719.0 + generationVersion: 2.824.1 releaseVersion: 0.10.17 configChecksum: c6b2bd1231da8dc3af5be7430f3cfbac persistentEdits: - generation_id: cf5f8e93-93fa-474e-8134-e9d24a4dee16 - pristine_commit_hash: d1602490f01343f2134e1de107c471bda19bc861 - pristine_tree_hash: 5d68f26e26ddbc41ea98a536ad5f711c9fa7dae5 + generation_id: f036c341-af7f-424d-b5c5-30b3e9b172a1 + pristine_commit_hash: 5127dc545cc26086977773862cd85d97ea348a80 + pristine_tree_hash: 6345cc3cfa59bf954c97f04795550fd94caa4e6d features: typescript: additionalDependencies: 0.1.0 - constsAndDefaults: 0.1.14 - core: 3.26.42 + constsAndDefaults: 0.1.13 + core: 3.26.27 defaultEnabledRetries: 0.1.0 devContainers: 2.90.1 enumUnions: 0.1.0 envVarSecurityUsage: 0.1.2 globalSecurity: 2.82.15 globalSecurityCallbacks: 0.1.0 - globalSecurityFlattening: 0.1.1 - globalServerURLs: 2.83.1 + globalSecurityFlattening: 0.1.0 + globalServerURLs: 2.83.0 globals: 2.82.2 hiddenGlobals: 0.1.0 methodArguments: 0.1.2 - nameOverrides: 2.81.4 + nameOverrides: 2.81.2 nullables: 0.1.1 responseFormat: 0.3.0 retries: 2.83.0 @@ -52,8 +52,8 @@ trackedFiles: pristine_git_object: 113eead5093c17d43a46159132885318f281a68a .npmignore: id: aa70c1f807c3 - last_write_checksum: sha1:6572da81f2e7a978ec29c22c2210bf461c5897b2 - pristine_git_object: 670ac0a62d7e568c9d44a14f5deafb8442a255d8 + last_write_checksum: sha1:3d5eb92f81539175db1ff0280e0aefbdd701f200 + pristine_git_object: cf98a6bf092538eb10ff0edc915102682ce9a6e6 FUNCTIONS.md: id: 21b9df02aaeb last_write_checksum: sha1:f8b9273977606e6c5bc3abae9354a5186af43ec0 @@ -96,8 +96,8 @@ trackedFiles: pristine_git_object: 39e2e27b82c7b641f0a498550c99017a00321785 docs/models/attach-action.md: id: 8de20a26b9b9 - last_write_checksum: sha1:44c1a79c9374746d732856aa68bf5844c148bdf8 - pristine_git_object: 44a90e2a8dba64514f85c88d7e9a287592fadf84 + last_write_checksum: sha1:45b557ec09fa5c493f57b8b9a6e8b563e9be95b5 + pristine_git_object: 9f0b94f97323321007e3fac6860afffe84f0b799 docs/models/attach-attach-discount.md: id: c10677064b44 last_write_checksum: sha1:f0095ac832df6fb4cc055b56d017207985a88adc @@ -120,8 +120,8 @@ trackedFiles: pristine_git_object: 376a0838c9e0cfdfe6ecfbfd2508353db485a627 docs/models/attach-code.md: id: 14b09292cbbc - last_write_checksum: sha1:3e916de85430e347c1a3594a8b68da273b5ad036 - pristine_git_object: fd60d9f2ea9203a756954cb671eb4931662820b8 + last_write_checksum: sha1:ee9ad35bc847c0b15c401e25610988b5f03db49b + pristine_git_object: a72bf7b95871aed0e16e742eed4be39b8cbdec8e docs/models/attach-custom-line-item.md: id: 0968ebbf76e1 last_write_checksum: sha1:4c493a8fba5e774a59206c47f63bef7ea4f9d613 @@ -236,8 +236,8 @@ trackedFiles: pristine_git_object: 1a31544c486bbdb05784f5f44d93dd8551f5a8d5 docs/models/balance-billing-method.md: id: 6ade18c9eaff - last_write_checksum: sha1:a53e1692058149acb627955e905176078f3efd97 - pristine_git_object: e895fa15164faf35ec3b60fcebe2a167c1aa1b2b + last_write_checksum: sha1:5a53ded38d42283a77c4a67af5e26fad8ccaf59e + pristine_git_object: 7b27f1d8239f0f01053f50f98da0c3709015444c docs/models/balance-credit-schema.md: id: 36fcc4d66629 last_write_checksum: sha1:b603a6d01cb2d65cfb542cae9880bebcd85d5928 @@ -252,8 +252,8 @@ trackedFiles: pristine_git_object: 7c450f9bfdabb817ca1bee6b9020453d6d9bcd17 docs/models/balance-interval-enum.md: id: dbc34d625754 - last_write_checksum: sha1:4238eeb6473efb2780568f1732a83087cd5c50ce - pristine_git_object: 7db0c2dd5c0b7ebb652b160074b92a53f85a29e7 + last_write_checksum: sha1:f90fb401c0c271e9473159272826a3f96905c0bf + pristine_git_object: 823d2ea29ce9abd3e1be7dba10667b7883e91b14 docs/models/balance-price.md: id: 0befed5ea384 last_write_checksum: sha1:fda6ad63bfebad099b4f9714d1b0ff4b8f064ffc @@ -268,12 +268,12 @@ trackedFiles: pristine_git_object: fe958e38daba860a25025cc1210cfc4327d8803f docs/models/balance-tier-behavior.md: id: 49fad040b38f - last_write_checksum: sha1:5c91cc7adf35dafa7e966d2d35937f92a46d3d25 - pristine_git_object: bac50dabd4fb38447fa593172f8ea3113a86fb4e + last_write_checksum: sha1:e0e8bf8c3965f936b559af3e25997bbbc18e4150 + pristine_git_object: 30c5de4a1922a1de4df750fb121e3f6857391f5c docs/models/balance-type.md: id: 193fed65f06f - last_write_checksum: sha1:da3f571fb4896b3d3caa20bf33c9c33c36f6faf7 - pristine_git_object: dfda3847ec0df4a8b917d5746722fe394b239258 + last_write_checksum: sha1:70e7a5881b94555a87d661714581bab07713702e + pristine_git_object: a7af331f1bddeb56774fc38e77fcb45959c0f558 docs/models/balance.md: id: 9de59403580f last_write_checksum: sha1:8d5433ac6435693a81086e91ba42d7ae173e2a45 @@ -292,8 +292,8 @@ trackedFiles: pristine_git_object: 68e5a3a00ee61c31522d695c2cdb26e2917f86be docs/models/billing-update-code.md: id: b84ac28f1591 - last_write_checksum: sha1:42971aa6160a71ae958639a336f749df58147eb2 - pristine_git_object: 7c5c0d332961e51e90e875930bc57fc5769f66dc + last_write_checksum: sha1:f884ec08c9f953fc2c2ccb85d0bdc24a71d1568c + pristine_git_object: 167a5a0db0d9f92c36f1f181e642a246753d0210 docs/models/billing-update-customize.md: id: 533e2ca5e4bd last_write_checksum: sha1:072993bc0384fd9debf9f72c717c659388dde340 @@ -358,6 +358,10 @@ trackedFiles: id: fcbf404063bc last_write_checksum: sha1:13335299452527b6e5accf8440600cc12bdcdc3d pristine_git_object: 36cdcc2b2d8a6a60d11ca36deac68d8f30b97d18 + docs/models/billing-update-recalculate-balances.md: + id: 8ead4615c410 + last_write_checksum: sha1:faeed44cbca8f10ff5e55213ce959bba7da80fa5 + pristine_git_object: 6c0732fded727c346294af65ef75e78a030f31ba docs/models/billing-update-redirect-mode.md: id: 6bd734fe582b last_write_checksum: sha1:bad9174a690d6c72dcbcf71e6f349c04ad4616d6 @@ -408,8 +412,8 @@ trackedFiles: pristine_git_object: c77bf103d178ca28a3192f153a676e80735904b2 docs/models/check-env.md: id: d46376d0c4e0 - last_write_checksum: sha1:79f6d930209b23c2bf5eb7ef468feeb2cf92f7b3 - pristine_git_object: c91506826d1df807c7e52fc71b93d43a08e64356 + last_write_checksum: sha1:fb17dcff1735e72d8f17c8a8ef3ddd6b7970d8bd + pristine_git_object: b232d2049d3acc05d189413f685362c377b0c4a4 docs/models/check-feature.md: id: 17698212d17f last_write_checksum: sha1:7da866b793c5b1dc17acbe43c867a0bfcf1d988f @@ -424,8 +428,8 @@ trackedFiles: pristine_git_object: e7a1e28ebd8cb19e0fd665f56754c4252ced391b docs/models/check-interval.md: id: 2b947e7da9da - last_write_checksum: sha1:a45ec5c1aa13d5d55190e3d5defb98dea5e56b28 - pristine_git_object: 7d90a519f2488653231e338d3d87749b6dd3a429 + last_write_checksum: sha1:f3760390659d8b08407d5dda25b3c45fabb33fa4 + pristine_git_object: c0f62b4515395a9594e22d8ee35a26953c53bf10 docs/models/check-item.md: id: 65ebecbc13ac last_write_checksum: sha1:faf9afc5dc6fc4d31ea5b4f4cfed35ffd538bd72 @@ -436,12 +440,12 @@ trackedFiles: pristine_git_object: b21ba54a0652e2c016fcb4434880c429fa4c41b3 docs/models/check-on-decrease.md: id: 361f05432960 - last_write_checksum: sha1:4aae28abd12eeccf2ee789c3684cb06052776115 - pristine_git_object: e08007c0ffd502919dd23fab540d0ce3ac92150f + last_write_checksum: sha1:218fe1ceed4ce70d3ab91ee5f86430670544df50 + pristine_git_object: 7401f77ec62b2981202cbaf4bbf2ffe3379f6bad docs/models/check-on-increase.md: id: 9f889141cf7b - last_write_checksum: sha1:539207de265a87ce63f2b37cd9a88d5dddc5ce8b - pristine_git_object: 985357bc0cc5110a25535a26fe123d923eccc52b + last_write_checksum: sha1:eb4f641b50be10d3316210c01a804d260373c3ab + pristine_git_object: 05a41c5e3b43f5b437df88e5c0b36e4a27efe8fd docs/models/check-params.md: id: 1f4a01957fbe last_write_checksum: sha1:c8c546b4aa4d1e459aef8131e522e6bf41ccc7c7 @@ -460,8 +464,8 @@ trackedFiles: pristine_git_object: c659c5a5a8df3ae58a7a467f513a905104e32cb6 docs/models/check-tier-behavior.md: id: 89be3843344e - last_write_checksum: sha1:942afb288468d96e6b14d3ae855b8dfbc2540083 - pristine_git_object: 77d39923ac11253f4bef2b2a581c9a1c6e634393 + last_write_checksum: sha1:5c5067037818a65556151b41c57cce608eac005e + pristine_git_object: 28423762d1e208e3464b8df9e0f10a28813c14f8 docs/models/config.md: id: bef254bf823c last_write_checksum: sha1:ee3a8a70d128bd4c6169a1ec01b9b53c2e075695 @@ -488,12 +492,12 @@ trackedFiles: pristine_git_object: 5a3f8c765ccd682f833dd305a9705770b8cae73c docs/models/create-entity-billing-controls-request.md: id: 1f94d07a9fe2 - last_write_checksum: sha1:b9caaeaddf6ccf964a62c8d7b6853029c7630b4c - pristine_git_object: a24bd89735a764c91bc40e2ea8226beed54c5667 + last_write_checksum: sha1:84b487ceb158e28c86bc02641f926865e38e3291 + pristine_git_object: 79879f470c36f6d35c54e142111c8c2197db7b3f docs/models/create-entity-billing-controls-response.md: id: 7048bbbdf39c - last_write_checksum: sha1:b42d259a20f81cc1d82bf0df57fdb5e6af7c333d - pristine_git_object: 658491c4ae8868549322e92e9a052702aece57b4 + last_write_checksum: sha1:be8e2bfe0482509cb860fa1dae192434ea4ca93f + pristine_git_object: 982f0f6c8d9bacfa0621641ab8179a1db7396702 docs/models/create-entity-credit-schema.md: id: 714d3a17c438 last_write_checksum: sha1:89c6f20f1230426553e153cdc3d0dddaccaa9725 @@ -504,8 +508,8 @@ trackedFiles: pristine_git_object: bd189cd3c9c870423540124e944760e44fc347e2 docs/models/create-entity-env.md: id: bdc2d4cbca15 - last_write_checksum: sha1:8c83dc475deaf8c560c582396a3d2278cbf228a2 - pristine_git_object: 8e59ea8fc2938ea30a6e9b582c44a4b5cab2a46d + last_write_checksum: sha1:7e8a20e31ba746e91bfadc5212e4178f3a3d5564 + pristine_git_object: 923094468b6a6435f41c1f9d831f345ac8a73012 docs/models/create-entity-feature.md: id: 53cedd277218 last_write_checksum: sha1:190a8d28cdead2c5bba81d15a0afb9a748825e9f @@ -544,16 +548,32 @@ trackedFiles: pristine_git_object: ae0a7e03eb4e2ca90cd529e911a8ef49b0ef4452 docs/models/create-entity-status.md: id: 658f1e6dd4d5 - last_write_checksum: sha1:0aa5d631d7d9ba0d9a0a42870153e7242b5b2ced - pristine_git_object: 788878d27119dc82e2877df317b425a1599a7f12 + last_write_checksum: sha1:72bf7235eccc3a1a1d871e28628e0a2ae6d689d3 + pristine_git_object: cf476e13ae64e821dc99420ba57aaf3b3a6d3262 docs/models/create-entity-subscription.md: id: d07f3f230823 last_write_checksum: sha1:52cca9d30e75b5a15325cde2003f67926dba0ab2 pristine_git_object: d3e9d13f1321d7af478fee7983ab2af4e4a647c4 + docs/models/create-entity-threshold-type-request-body.md: + id: e8502fdc1235 + last_write_checksum: sha1:fe909bd46f12501ccb64ae91d31355106e5d7933 + pristine_git_object: 37b9972da7d35cd2790acb7bf926a59e2855e49e + docs/models/create-entity-threshold-type-response.md: + id: 1248df87dfc4 + last_write_checksum: sha1:74e57a1ccb688b5c438b02c50ea45b67c9722a30 + pristine_git_object: 7614c3b6af77285fbc0c5f79b8d18b681a23c831 docs/models/create-entity-type.md: id: b7fd22bd7861 - last_write_checksum: sha1:a0f47b4b77657a52ac54e3ee07b553675bb9dd5c - pristine_git_object: 99caea31ac40ddd636425c0c559b31302d494cb3 + last_write_checksum: sha1:dcdeebd80668336b62034949f37c4f2465981337 + pristine_git_object: 8528c54987e315edbd01a6486b1e48e57e5a63ab + docs/models/create-entity-usage-alert-request-body.md: + id: a3441743c411 + last_write_checksum: sha1:e5b8794180d19bfd9bdf7df373c96d1f3be17990 + pristine_git_object: b248b2a832f5758f4d6bd099b8963be60002461b + docs/models/create-entity-usage-alert-response.md: + id: b4c50866191d + last_write_checksum: sha1:ea12949d17cbad703aae1039f64af4c36e07c4dc + pristine_git_object: 6366468abca89170abb2d5769ecb01ea3b205ba3 docs/models/create-feature-credit-schema-request.md: id: 12282f670bf0 last_write_checksum: sha1:68cb7e44ce4c8e0a8daacf13e2cb0e5afecdc172 @@ -588,20 +608,20 @@ trackedFiles: pristine_git_object: 59486a5f87ebfb477b8ac9c379cafe031fc1084b docs/models/create-feature-type-response.md: id: ec96747ac5a8 - last_write_checksum: sha1:a02cc9462eca35ec9d6d4149cb145a15be09679f - pristine_git_object: d45c54c26108de2eae9d7bcd881db79fac927fc5 + last_write_checksum: sha1:e8300fd82a995fb3db3c08fed4ee600222210174 + pristine_git_object: 13c8499a703b03b5aaf94deb471298fc8f4ccfe3 docs/models/create-plan-attach-action.md: id: 3a9b41faa0c8 - last_write_checksum: sha1:570d06010522edb4dabb3c27b26fa66aa2fcf909 - pristine_git_object: 11bf4a9d74e75a895e27489a55444fbe76d6488e + last_write_checksum: sha1:715383937d39ebf974fbe45bfa3f4fd75456c4a0 + pristine_git_object: a72358405d0c6c0b9815c09a60433b1a4e657006 docs/models/create-plan-billing-method-request.md: id: 5910c31fbd40 last_write_checksum: sha1:f760605fbcf453ca76282db0ceb6c56dc471198c pristine_git_object: 881afe6799b816389f7726b398c8703a5b43a1eb docs/models/create-plan-billing-method-response.md: id: 4b62f4430e70 - last_write_checksum: sha1:6f14cf6ec48162bc6154580375c55f699b9925a1 - pristine_git_object: 5a51ad5a3c5938a475f94789e10e56e93aa198f5 + last_write_checksum: sha1:760f8f63af3a0d880434489aff0ec69274205ac2 + pristine_git_object: c23b3e14cc3a3237db918db21e5a166f8948cd1b docs/models/create-plan-credit-schema.md: id: 213fe4cef877 last_write_checksum: sha1:9a9109b25682ec0ed4db881cf449e6ed823eb124 @@ -616,20 +636,20 @@ trackedFiles: pristine_git_object: 61eda90ca32639a989eab7ac744fa9aef0f827f6 docs/models/create-plan-duration-type-response.md: id: 7037528d7fd6 - last_write_checksum: sha1:dda35c0d789cca19ae6ec04493207f2931cfd2b0 - pristine_git_object: 7a02afe1f00c2a708d53363eff5668ebce86eade + last_write_checksum: sha1:b9ca0bae4d08cd78deb0a63ce58c057409101ef9 + pristine_git_object: 77c170b36f706ff57cdd83edd6fd9be065251695 docs/models/create-plan-env.md: id: 6cc4a7432037 - last_write_checksum: sha1:e4c71f4bf1bd9c341e40da5e7c1a5da14fbf7fad - pristine_git_object: ebe0cbac88d8f8430f6d870409e8e4e17f6ec7d2 + last_write_checksum: sha1:f5bfb3f0cface216095c8113fecfb6ed7b847698 + pristine_git_object: 54bc47b8d3d4e60a3f95f324a87aa2598da3ac62 docs/models/create-plan-expiry-duration-type-request.md: id: 64f50835e6bb last_write_checksum: sha1:5567b9305c2c0a0eb6a0507b62b5f1dc8569dfe9 pristine_git_object: e24e9550a7967f519354812a14490b5a2e6c3840 docs/models/create-plan-expiry-duration-type-response.md: id: c2e3d11324b4 - last_write_checksum: sha1:ac4d671900f88510bc21d82b4c4bc680d402836a - pristine_git_object: 4bfa66676478734a27953bcb09c4c09ad45e5ea9 + last_write_checksum: sha1:1163d8ba2d805ad45b966c6b463d3b0bb5ffa1c1 + pristine_git_object: 0e763d963d9478e1f59b696cef39340d701019ab docs/models/create-plan-feature-display.md: id: 77c8a90679b3 last_write_checksum: sha1:fbc637093c69e128250e5fc72e48dfc2ac319418 @@ -692,12 +712,12 @@ trackedFiles: pristine_git_object: 041e09cef3859854ecdf8997334bfdf94e732cf0 docs/models/create-plan-price-interval-response.md: id: 171e588628d3 - last_write_checksum: sha1:8684f1bae40c7b9b4dd50c234a014c69ffaf9c64 - pristine_git_object: 4681b6812d20c71b1e0f94d087127d375a047802 + last_write_checksum: sha1:a3687969c982d817d27534605aa63fdeed5c3a07 + pristine_git_object: c11dea424b2548d1935a0b217d098380e915e546 docs/models/create-plan-price-item-interval-response.md: id: f82a720e74cc - last_write_checksum: sha1:ab3d807d35c4d6a108593aec37e087c3efc54867 - pristine_git_object: b689fa821d998e5388d12f3fb7f8c1f21021f08d + last_write_checksum: sha1:8d3ea30be8b133d51a2524f169108e1bd2bac62d + pristine_git_object: 738eae0ccdfa30004d7a39b67b8a6cf290d1b138 docs/models/create-plan-price-request.md: id: ada0d70e716a last_write_checksum: sha1:1e5c54f88963da99a3cafa25d41bfd0a5b3fd8e6 @@ -716,8 +736,8 @@ trackedFiles: pristine_git_object: 3ab22b050c282b26f8e10b52d9cb96285e13f734 docs/models/create-plan-reset-interval-response.md: id: f48e94cb8d80 - last_write_checksum: sha1:4ac662e7e53d41d7d4082e38e6869391ca746e15 - pristine_git_object: 18e647441adf9d2f6bbde2a55c9b56748cd7ef6a + last_write_checksum: sha1:31ade0a5bec7f2706a11c61eaa7b00c01d4925a0 + pristine_git_object: 726d39c72f14a015954d6c7fa610dc56cdbc3df9 docs/models/create-plan-reset-request.md: id: d88e31eb9636 last_write_checksum: sha1:faa2ed1cdae4ae1ff4534b709c718e1a854c7ee8 @@ -740,16 +760,16 @@ trackedFiles: pristine_git_object: 5333fae7f3fb953439905529c246d31e90c0837b docs/models/create-plan-status.md: id: cb2e4b4b718d - last_write_checksum: sha1:5d4dcc420558a49ccdaf2931738ebbe09dfbb2f5 - pristine_git_object: 01165a04e3c5887958957a73150ea166229d86f7 + last_write_checksum: sha1:9d06e4c18d16af4e30f8599770874438206695a7 + pristine_git_object: 9176e8970812a64a9fa8f222cb804f98ad799e53 docs/models/create-plan-tier-behavior-request.md: id: a8b6b86a026a last_write_checksum: sha1:9ef8eb045da5b7fd0865603b7482d16f34dbe425 pristine_git_object: b3a2fb612213fe3ab0669c4c7e197ab83b3b2f36 docs/models/create-plan-tier-behavior-response.md: id: efafb363cb61 - last_write_checksum: sha1:98d171de5564d6e60e0844caf3ad8663285b70dd - pristine_git_object: 433e1c442a5f1c99bd755e45dc4b02e07afab7ac + last_write_checksum: sha1:8640c7a5a3d2d6d704b55c88e09481ce6da114ea + pristine_git_object: 8d87736b1e186c41aacc8e2f310a2ebf2fe6d817 docs/models/create-plan-tier.md: id: 9f8755fab2b3 last_write_checksum: sha1:9cef313a71dfe0ebcbfdfa3158e5a09cf380db2e @@ -760,8 +780,8 @@ trackedFiles: pristine_git_object: 86be2ef4df506af2e5f0886c0cb149453e6bfe8f docs/models/create-plan-type.md: id: b41ecee1f888 - last_write_checksum: sha1:f6b5067df1df12946d1d846d73d126ac5ebf2d73 - pristine_git_object: 0ed951484d4a3ca7527f3dd73320b24f3e4e3127 + last_write_checksum: sha1:d68e8655fd6692d7227e237051e746d4704f3055 + pristine_git_object: 6e9be0f142bba31c244a5c9e33b97bedb255f4a2 docs/models/create-referral-code-globals.md: id: 6e32bb0907b2 last_write_checksum: sha1:35e5d951bf5ff283f2c8325e02b597ec695cd0c4 @@ -780,8 +800,8 @@ trackedFiles: pristine_git_object: 675e2677bb14f3785175b8f0545c58f216d3fe0d docs/models/customer-billing-controls.md: id: 301a2b34ca74 - last_write_checksum: sha1:b99e2869f932eed0336a01f9a41448ed7c4d66e1 - pristine_git_object: 2cad5bcec72c06213ed5d70cbc833bb9b6056fe7 + last_write_checksum: sha1:93017e39c4ab759c8b149bc1924898914752e058 + pristine_git_object: c149dd4355a96397576326e7d9638ad58cf955b5 docs/models/customer-credit-schema.md: id: fa77c00fcafd last_write_checksum: sha1:ef98406b2bfd786c688c58331f8c60dd3768edc1 @@ -792,8 +812,8 @@ trackedFiles: pristine_git_object: 08690cfde833286110e149d4bc79a1dafa6d3010 docs/models/customer-data-billing-controls.md: id: ca09ea9a9d79 - last_write_checksum: sha1:96cd68586beeb120566f28fb0c93381b8fc0ce76 - pristine_git_object: d0e05395ded092f93d9a2829159704b688d7d357 + last_write_checksum: sha1:56444a8bd451fa1cd97d8cae818cb5f477d0da53 + pristine_git_object: 606866af27c288f4eec2cd2a6212de939543d575 docs/models/customer-data-interval.md: id: 3e52ae6b53d6 last_write_checksum: sha1:cc448a4ee6f5523b21d64f5e3c169626569d3161 @@ -806,6 +826,14 @@ trackedFiles: id: d6adb304f72d last_write_checksum: sha1:f510b4b0b5dcc6eb489318705078eb5683f348d9 pristine_git_object: 57c583eb651726cd311cbf33a1094773ff5bc1c0 + docs/models/customer-data-threshold-type.md: + id: 31ee69581c25 + last_write_checksum: sha1:54134d596ff7d386c5ac35300ab3c77376ebf118 + pristine_git_object: 89456db6d6ec404f7c652c332963de1a6517dba5 + docs/models/customer-data-usage-alert.md: + id: 4c36e645b4b3 + last_write_checksum: sha1:f531ce96ae322f2c8f4c8a615173199dd5238207 + pristine_git_object: c35e4aabef725a11d35c3ff126882cb94e7f908a docs/models/customer-data.md: id: 4561159f8324 last_write_checksum: sha1:a20bc91530f160598ed7556e1f44611e2a7ba49a @@ -816,28 +844,28 @@ trackedFiles: pristine_git_object: 72c256a9fe65486b7f0947ae7c4e1a08d307680a docs/models/customer-duration-type.md: id: cbfbb0769db5 - last_write_checksum: sha1:da368cd99ca260beb69f494102b503f0ef1fb131 - pristine_git_object: 3ec8623592871f2a2688f3c998d8edaa1fbd44e0 + last_write_checksum: sha1:5cc7fc548fc238c2c4db1517e1192dc036a74139 + pristine_git_object: 1e752c7056ae635cb6bd8aa5a72ee4a95db57ed2 docs/models/customer-eligibility.md: id: ca41bb82f9bb last_write_checksum: sha1:46c74c47a94a6c3272cc93f14c3e59513e73854c pristine_git_object: d116ce2acc6a2402cfbbfc03b68d6c83e655e689 docs/models/customer-env.md: id: f063b206890c - last_write_checksum: sha1:56f837d6ce27108c0ce1734d43119af75f9f6f76 - pristine_git_object: 654904fdd157bf55e25c3178991dd11d1317d485 + last_write_checksum: sha1:cc47133530e606c040759b3f18b48dad7b78cdae + pristine_git_object: d210083c532e8c527f089ea2abc2c227e2b7d8e2 docs/models/customer-feature.md: id: d90579a4e5c4 last_write_checksum: sha1:50870acf6a5fb9210beeed5c2c81e91a526d9c48 pristine_git_object: f38cdfd6ea5b5adec63eb912ca1339d50097614d docs/models/customer-flags-type.md: id: 42f5241c0bbc - last_write_checksum: sha1:eb1df3c4eafceb4af876a37ca91b6b0967abee2e - pristine_git_object: f5d8ac258d33cfd15ff581872aa4c0be9e9856a1 + last_write_checksum: sha1:aa84fa88f66cd8aae81a6e4d19c78a9910494f4e + pristine_git_object: 1484c10d794f0953afecf9ce7cc90ad3b2a9516c docs/models/customer-interval.md: id: df82a4aaf8e4 - last_write_checksum: sha1:d0b0e17f007ca5ac56800b6bb0906d6b5a39004b - pristine_git_object: 7f6886d54d124f6bef8f831bf084ded8da6ab8b1 + last_write_checksum: sha1:aaf308010396f889f64bbc0f1b6373a2b0be58df + pristine_git_object: 4be272a478676c247a14abd8118bf9c934916fb8 docs/models/customer-purchase-limit.md: id: 5bf7b9d4d318 last_write_checksum: sha1:6c6e6ad9afacda8aa5ea11f0866876dfcfa4b82c @@ -848,8 +876,16 @@ trackedFiles: pristine_git_object: 3658fb60429ebe70a39433584f8b991b2d04d9d5 docs/models/customer-status.md: id: e9ed998dbc6f - last_write_checksum: sha1:e2cbeeda5d8612ec18a98fd9e8a9c34af483753c - pristine_git_object: 30609b6ffb83a8ae805e29085c45246fefebb0f4 + last_write_checksum: sha1:11e549413866ee056d72b3c553da31b1d3391f07 + pristine_git_object: 20a2ee1d46ba4b86e4949d952e7dfc916177f056 + docs/models/customer-threshold-type.md: + id: 7b025bd502e0 + last_write_checksum: sha1:10900749d325726d95a259d942aa262aff2fcce4 + pristine_git_object: 90ab7fbeb380ccf210fd9b1fabca7a27703718ab + docs/models/customer-usage-alert.md: + id: 97223e162da0 + last_write_checksum: sha1:61314643433dbe105e3604247b17fc0659043ddd + pristine_git_object: 84f73f3108ecec7ee45c99c4fb9fa73863ee7c41 docs/models/customer.md: id: 42ac97d31359 last_write_checksum: sha1:da948098c6107d0f14bcf6f83f54868236d4e7bb @@ -924,8 +960,8 @@ trackedFiles: pristine_git_object: 1cd19465e5e2ccba85d1117383cbb3ee6cae7763 docs/models/entity-env.md: id: 817aa096ab27 - last_write_checksum: sha1:252538702571222f383aa430014bc61b99eb4a81 - pristine_git_object: 583f6eaf6f0c06982b6a1287fcbb81c17f0c6ced + last_write_checksum: sha1:e57c22c174cf8744c9fbc793a21c87eab86ba81a + pristine_git_object: ea4e92658978c0c7494a04f24e0f2e9276baaaac docs/models/entity.md: id: 903c73579a5c last_write_checksum: sha1:2fbd49394f3b36f26993a3d9b8a15da25f190727 @@ -940,12 +976,12 @@ trackedFiles: pristine_git_object: 032d2464b6ff1e4b2f1a35591a2014edf4ed21e6 docs/models/expiry-duration-type.md: id: 3a927f275515 - last_write_checksum: sha1:19a85259a30b26567db272e409038d45136a35c8 - pristine_git_object: cdf1d50ae086bd5778c6c266097c00619c0787df + last_write_checksum: sha1:c3248609945783ebe4b18bff68bec64bb1699694 + pristine_git_object: c622ff90557f1ff06fad783abed2c86415cfd372 docs/models/feature-type.md: id: c6368d6178e0 - last_write_checksum: sha1:6cfb774eadb97f33ce64f0e3a0d6a651f989b2c9 - pristine_git_object: 8d1c1037ac18f2073977feb666331ee662cd3ad3 + last_write_checksum: sha1:6505753d40a5ec1a42484e598418b79fe62b1495 + pristine_git_object: ddcb5323b6c9287ec6a5dd1d5ca65dee921c228e docs/models/finalize-balance-params.md: id: d27a9dc54b34 last_write_checksum: sha1:4a792e241eec2080f50ce35affdbe233f2452da5 @@ -964,8 +1000,8 @@ trackedFiles: pristine_git_object: 382a852fd69e97fa81176acdd66a374f5254399a docs/models/flag-type.md: id: bea64d93e37a - last_write_checksum: sha1:f347144e427f1c112728d1822c769c87fce21e6f - pristine_git_object: 7bb722b222e6bd30c8da293c4e3ab33005c0f14d + last_write_checksum: sha1:dbfa4f493bd520efaafd03628374cc19eb1efa17 + pristine_git_object: ad92646088cd31262986013299eb764f7711e832 docs/models/flag.md: id: f7a84826e2db last_write_checksum: sha1:68a6e6d129bcef481cfb50fc83955242d6c63edf @@ -976,8 +1012,8 @@ trackedFiles: pristine_git_object: 31ef9d606867dca40dea4bc8c751118acfb7077f docs/models/free-trial-duration.md: id: f12ff48f6deb - last_write_checksum: sha1:522ce4071e5cacf6aed1573e6b9c4ef8114de6f7 - pristine_git_object: be7e436b27f6c19e74687875395980c34840b330 + last_write_checksum: sha1:dbbd74a45263a761d7c0e14e8e743e34aca1f19a + pristine_git_object: 1d6b973f2eb7216a623c8ee0a41253abd993eacb docs/models/free-trial-request.md: id: 746a54d9b71f last_write_checksum: sha1:3e9f8119181b6d56ed4f0021f8f97766996ffb04 @@ -988,8 +1024,8 @@ trackedFiles: pristine_git_object: f9f3e74e3e6f989e12f5ae4ebbcaf98de2a2e6ff docs/models/get-entity-billing-controls.md: id: e5f62ec2d730 - last_write_checksum: sha1:6131fe9997d4772d5026f74a9bf7e142af77e9c9 - pristine_git_object: b2ce4bcef5cca2441f8d6aa94e1ed3a0dbb68ca7 + last_write_checksum: sha1:9fb89839a8eb58fd962052f326d1f91730166fbc + pristine_git_object: 90d9420da3a8ca814ab31c5737ec68c1f1ca96c6 docs/models/get-entity-credit-schema.md: id: 6f32eea7abc4 last_write_checksum: sha1:42a082402cccd20654f96e6af78f165d340b8324 @@ -1000,8 +1036,8 @@ trackedFiles: pristine_git_object: 9e676b05370fb3301b52b7edf3ac386eea2ecaf6 docs/models/get-entity-env.md: id: a0aec49ca920 - last_write_checksum: sha1:77edfad129919a116f78a82d247a81687cb93e2c - pristine_git_object: 8dcd9a68d2142e9d5fc73e97b0f6529641c45cf7 + last_write_checksum: sha1:dccda9677d4762959de028b0008965c35968f846 + pristine_git_object: 5490fe5912db6de66a4cfeb05914e41db0cdf110 docs/models/get-entity-feature.md: id: 61710e83f226 last_write_checksum: sha1:ae045ca5a705c07a4cf28d8e8794682ddcc53c24 @@ -1036,16 +1072,24 @@ trackedFiles: pristine_git_object: 065ca1896d65d047ef5b955ec5bcae1c424ed4e2 docs/models/get-entity-status.md: id: 06f4024e9973 - last_write_checksum: sha1:c8b7df3b5f53540a93ad059be38d7f7f8abea440 - pristine_git_object: 525aa382f4566aa0945589bb2f76bd30dc734080 + last_write_checksum: sha1:1b1e20ff2b6580f8db11b441174e9d9ec923d2f4 + pristine_git_object: 065cb9fd1c823066fcc5e8b1c353eaf607cc82a4 docs/models/get-entity-subscription.md: id: 3c768c24c069 last_write_checksum: sha1:7c4324295c741949236c978bc968ae51e8d8f570 pristine_git_object: 6dc404626b7beea1ba32c01269a9debd4533edee + docs/models/get-entity-threshold-type.md: + id: aeffe83b54ad + last_write_checksum: sha1:a6a6ae317ef4ee35c0d9c670dd61d6813b70232a + pristine_git_object: 9c79acd3f9b34c9283cc78b65b22b9f3bc5adbd3 docs/models/get-entity-type.md: id: a4327c9850bb - last_write_checksum: sha1:3b87a7bd5465a9fdd2019145225799140a136436 - pristine_git_object: c464e454b6a0e4a3ac2c1c2ffd949116622d6c70 + last_write_checksum: sha1:fecaad9042b8619a1603d30d58255f5177519781 + pristine_git_object: 94ba6334208e020988a681dad64accc7db48116e + docs/models/get-entity-usage-alert.md: + id: f9d9e7763fc7 + last_write_checksum: sha1:695ec9f5a6600a3b961171787fb65dea2630f1aa + pristine_git_object: f842aa7fc2de506852bfe5bbf200c579b9ad9fff docs/models/get-feature-credit-schema.md: id: 66fa50a914f7 last_write_checksum: sha1:1df7f3c8c99b23f0025f2ddf44923b64cd50fe8b @@ -1068,16 +1112,16 @@ trackedFiles: pristine_git_object: d16228318cf121178c15d0e0856e659c946c6e9c docs/models/get-feature-type.md: id: adf0af07d3cd - last_write_checksum: sha1:b372f582c4ae8bd51c95c8018aaf03142b47d35b - pristine_git_object: e0b36186fee578620f2b18bc58b518541b35b7ea + last_write_checksum: sha1:6f3f919b23692a2dd79903a0c5731f7b32308410 + pristine_git_object: b6f5d8682632257e68d9c66bb9cb4c6552d7f696 docs/models/get-or-create-customer-auto-topup.md: id: "673761036094" last_write_checksum: sha1:24beeb4b836370993abaed525ccf19fdb06ff9c7 pristine_git_object: 6df7e31ae12682659c51e3257d70078858179003 docs/models/get-or-create-customer-billing-controls.md: id: e7ee10d9a50b - last_write_checksum: sha1:e599f0c656c9d65877c0c00074665b539704a2b4 - pristine_git_object: e7e44bd53b080f1a0c27dfc15c909fd0e64f2a6b + last_write_checksum: sha1:6afb2e2879f8a0fba53be5820b123d106331c93e + pristine_git_object: 45f6c861de029ebecb414b6e0a02321400997675 docs/models/get-or-create-customer-globals.md: id: 4dd584e66362 last_write_checksum: sha1:2f39158f4225bc4e4fbc5507304a0654c7a2af79 @@ -1098,14 +1142,22 @@ trackedFiles: id: 6bc905d17111 last_write_checksum: sha1:a1c4a90b02fabc01b874185de0d3aa140aaca40d pristine_git_object: 232804422517c0aa5ed2ff5bb9b8ec77840d4a99 + docs/models/get-or-create-customer-threshold-type.md: + id: 766bad11b2d9 + last_write_checksum: sha1:99c0f5113d5aa2e794b9dd67e479bb268af6e692 + pristine_git_object: f41a96ea31c804d3c2dbc78959a3e2603cdf666e + docs/models/get-or-create-customer-usage-alert.md: + id: 283e702fbdc1 + last_write_checksum: sha1:b57c3b716aed0ceab82f9ebcba9374d056ed50c3 + pristine_git_object: 2fec2e0823cd0191f91e5c230c103ef04b0912e7 docs/models/get-plan-attach-action.md: id: 6a535e94ba23 - last_write_checksum: sha1:568f646609ada6451a989afa855bbb2ca40a628e - pristine_git_object: 67cd113927bb4350810e7301e0d0b33e61817154 + last_write_checksum: sha1:d29bf782658f15fa9a1cf4f2e461b580e6dfaf5b + pristine_git_object: a30a9edfccad50810206fafb99234bf1cfd5390e docs/models/get-plan-billing-method.md: id: 81d8a111a09a - last_write_checksum: sha1:25fc939c3c24f1e4fdee1ac9f9668dcaea817291 - pristine_git_object: d7318d7d8db471de2c14e9f80225da5437dc0aea + last_write_checksum: sha1:af53690dcbacd8d9de1b8d0fd3ffc8d84b9bffc6 + pristine_git_object: f5768604cf8923efd72ddd1d24c7bae63b4a01e2 docs/models/get-plan-credit-schema.md: id: fdfd3a232487 last_write_checksum: sha1:bda036d0335542dc4bea36444ead3b4d1fc31c6b @@ -1116,16 +1168,16 @@ trackedFiles: pristine_git_object: 768d96909fc8180ee25a600550a9db192980341a docs/models/get-plan-duration-type.md: id: b69c3a89ed38 - last_write_checksum: sha1:6283a7934599cc7dfe411b90b354e15da3534021 - pristine_git_object: 9ed1c3f531807594ac3d3dd21c59bea0e6496ca0 + last_write_checksum: sha1:4d302f73a60464b04f4e308e464d3011868b3a55 + pristine_git_object: 08738e384e682b3df3764eede7f159535456fd11 docs/models/get-plan-env.md: id: 9c45998ac325 - last_write_checksum: sha1:f87836e8d3cb3db588f76938d3914b4b6fddef79 - pristine_git_object: b8b049c88110c8c3adc3d80c85b33597d46c7684 + last_write_checksum: sha1:890b15667716ff79fff0baa98d50fec4f9a963e3 + pristine_git_object: 502bfcc4939437519274d07cd5fa6c5bbd0dd091 docs/models/get-plan-expiry-duration-type.md: id: 206c98bb07a5 - last_write_checksum: sha1:81c85b9a298b808eb1e07ac1d85440c7da18a553 - pristine_git_object: 00e529721f22a1cd26ce3f6fb6d77034c5904dc6 + last_write_checksum: sha1:c348f23836894aa52e0c31d388e30281efe2d4d9 + pristine_git_object: 0b633db6c9efd1cf814354322813f0082081f213 docs/models/get-plan-feature-display.md: id: 5cc0dba3045a last_write_checksum: sha1:6e2a60793ef7e7a29f48b393ccc01d72643ea9a9 @@ -1164,20 +1216,20 @@ trackedFiles: pristine_git_object: a385e4971e5561943d86db76c3bf11fb6f024a54 docs/models/get-plan-price-interval.md: id: 9c62d7ee142b - last_write_checksum: sha1:d3b369e876d47f4f9aabcbc36b113481e2ada71a - pristine_git_object: f1f0994945733efed1b848a27b297c008db0fe71 + last_write_checksum: sha1:6a0e5cdaeb26bb7886e0a60f2ba76b4420ab1669 + pristine_git_object: 63a258d288c8a45cf55600b5ebbd1c6c6d64f1cf docs/models/get-plan-price-item-interval.md: id: a6f9d83e7977 - last_write_checksum: sha1:010243e4bc3aa33bd04a0180ea32ed488fed365c - pristine_git_object: 3f99aee7fa7da9240724cc0f8937ae207163d267 + last_write_checksum: sha1:a4d16145daa8c651ecb1021f7676edf990a25c0b + pristine_git_object: af0c14090c03fda28711dadd99a1f54bba08141c docs/models/get-plan-price.md: id: c023219918c5 last_write_checksum: sha1:df9fad9b9570fbc20e35f04904f7cdc72bc471fb pristine_git_object: 8cfac5aa0600ecd1b8a5824bdfa278901ef66195 docs/models/get-plan-reset-interval.md: id: 1946366bc337 - last_write_checksum: sha1:9b8e321a6cfa15b9414b8a4abb7195e6b856512b - pristine_git_object: 7719fc58d5f440fbd2054f5aebbce3389f65b0b4 + last_write_checksum: sha1:d14fb798d8a7653d86ab2aea1172817e78260352 + pristine_git_object: 56d62f31decd85f39503c155e8d285add8b8918c docs/models/get-plan-reset.md: id: 271f23d3ba6c last_write_checksum: sha1:d80f52d6e012e26551f2fc6e49ad25dd2a22d423 @@ -1192,24 +1244,24 @@ trackedFiles: pristine_git_object: 2ed20d9be6b31bd70b2d30f0b517cd4470cf6273 docs/models/get-plan-status.md: id: b2b6fd3f7f36 - last_write_checksum: sha1:57e8addd1b9b62b57a14a4ffbb5e25d83f3d0dcd - pristine_git_object: 4d5fab597cb8830b619c94f124a30ac6fb6d3087 + last_write_checksum: sha1:677539b6b32bdb5d489ca1465017b3762e608740 + pristine_git_object: 6393a8749c562bf17a451dd2968f9d80ca1c5726 docs/models/get-plan-tier-behavior.md: id: f3718ee31687 - last_write_checksum: sha1:7fd442b40cca24b5b2058587b86b8343791549d1 - pristine_git_object: 4e14d9501022fe440e86ccb830a2caaf90352859 + last_write_checksum: sha1:1a2f2d68d35dd37f988476bc96bdffa2299d63eb + pristine_git_object: a03d5171735676a42527f85a76a9663d70890c4e docs/models/get-plan-type.md: id: 5300ef539ed8 - last_write_checksum: sha1:b334efb80b4e13e356c43248994d2f2640ffcf27 - pristine_git_object: b6ff1ca2773ceb3a33da7acc3bf2144f2823c16a + last_write_checksum: sha1:71adbae5b4d49f8e04676cf58a35318bae15ac48 + pristine_git_object: 75cd05fc9753020eeda5607a4cdefb0723b03482 docs/models/included-usage.md: id: f27abcdfdd7d last_write_checksum: sha1:ca0a41260fa62322a9525f4267ac0ec6d93ae6d8 pristine_git_object: 5fe6a61067cd319d6cd0d1e1e7f1d165c0d4579d docs/models/intent.md: id: a4a6fa8818f7 - last_write_checksum: sha1:39fa3a78bda572fa9b8a765966413b6d2b4436ea - pristine_git_object: e555a3ae8dede23c40e8f431e1b6101cf5e7f21a + last_write_checksum: sha1:eb7820865f56b87283859213490921e05cff1795 + pristine_git_object: 23c92340b59eda49f7ef7aa97bb9035a00bf6f20 docs/models/interval.md: id: 11a74d2c19c0 last_write_checksum: sha1:5f0ef8c55bb702634292f728b5abab0d9a0b9319 @@ -1228,8 +1280,8 @@ trackedFiles: pristine_git_object: f60f6ca4e299bdea107323dacb9349333bd62500 docs/models/list-customers-billing-controls.md: id: 28916a0d81f3 - last_write_checksum: sha1:402e141c7b2060d74716e16fd28f453a67abcf0e - pristine_git_object: 5b85d9e7e499c58d23d6179d35e4c1b74c0db9d1 + last_write_checksum: sha1:066331d3ade1c88fcc6c667650a2dcab3608b4d5 + pristine_git_object: de1786cf1660db01ad9db6d7887a4a670aaa5e55 docs/models/list-customers-credit-schema.md: id: bd8c18aa56d6 last_write_checksum: sha1:c5e78bcd3c4e7685e81788f23ab6251269f649c7 @@ -1240,8 +1292,8 @@ trackedFiles: pristine_git_object: cb09afda7dd728b185e62165bedab7c2ae878c54 docs/models/list-customers-env.md: id: aa422f2f068e - last_write_checksum: sha1:1eaa89b04ceb126d8854095fae4a375ad3b6690f - pristine_git_object: e8319e97653a42eb67a6e54d154471cde8182df8 + last_write_checksum: sha1:2813f4b038dd5a0145a4904890f8e87bc83ae53b + pristine_git_object: ae3dc7884d8884306e3df9310c093ec681637681 docs/models/list-customers-feature.md: id: e66feeb7ba38 last_write_checksum: sha1:6d64076d70976a5807411ec74edf56dddae486e8 @@ -1256,8 +1308,8 @@ trackedFiles: pristine_git_object: 8b5163cedee225b4b80be05037d7267d2f952b6c docs/models/list-customers-interval.md: id: 0cf67d0395ae - last_write_checksum: sha1:ca79dfd9771a32ba36f0c77c7fff79152a204e94 - pristine_git_object: 1f0bc2955b718a93046892f7bf5593afedd5a730 + last_write_checksum: sha1:bdbd0a010da0ab3d0c5a55c6d8b5d4c1f5e70897 + pristine_git_object: e92cca5d7a0d39aa5ac3245ae4197ab4db830cb1 docs/models/list-customers-list.md: id: d9cf28563990 last_write_checksum: sha1:22c3d4996dfd7b25f65919c4f634295b34567e37 @@ -1288,16 +1340,24 @@ trackedFiles: pristine_git_object: a0bcde52449c9b05ec2ffbaa3fd81ea911a1dbd1 docs/models/list-customers-status.md: id: a101fc59dad3 - last_write_checksum: sha1:d17ff581b24d4b6ae97ac01f672c6ccd2619e1e9 - pristine_git_object: ebb49c197b1cec0e7efbfbed9ff9d1429a5043e5 + last_write_checksum: sha1:7b6f527d630b8733488c3527c6097ab8ffdd1c88 + pristine_git_object: cac7cc97ab962419fca75b9d9d2982d25343f5e9 docs/models/list-customers-subscription.md: id: e5c39bdff7de last_write_checksum: sha1:2149bab9fbc402075c6e6fad9227da4d934f6354 pristine_git_object: eebfb64ba72352c3bdd046c5d524791b545c2c2c + docs/models/list-customers-threshold-type.md: + id: b31853748775 + last_write_checksum: sha1:f359dda63ff1705871ce3344f33ae07e3f4eeb5c + pristine_git_object: bb811c0e2545ab22359bc414fb108867599c8d32 docs/models/list-customers-type.md: id: 0800cbc8ca9e - last_write_checksum: sha1:ce2e02b77c93fceb4112a25522981cb9ad28da4c - pristine_git_object: a8572a253eaff23800e32b97966b27024a175d00 + last_write_checksum: sha1:2305f2fbfe8e5dacd1feba04a09abe8ecb6cd9c1 + pristine_git_object: 684d4be1ed4b78ef115955bbbf9b4650f2d7e93b + docs/models/list-customers-usage-alert.md: + id: b3eb5cb1a703 + last_write_checksum: sha1:1b748d9d32129465a66933c5dc33f1a0e0eef542 + pristine_git_object: 5c72a5e92a135e3759dbf9a0c04d327672e09693 docs/models/list-events-custom-range.md: id: 61ea802fb875 last_write_checksum: sha1:29739e8653c6bc2cb893b8c5fecd7503436f91ad @@ -1348,16 +1408,16 @@ trackedFiles: pristine_git_object: b4ab86d4b5ca500d8869996c31423005554d3bae docs/models/list-features-type.md: id: f90d587b8227 - last_write_checksum: sha1:92e406420f065240cc86249766dd22cfecdbcae1 - pristine_git_object: 2d4d45ea57e3eae7530b2907f918e7f021f70017 + last_write_checksum: sha1:a2a161ab7484bdc88a761c9f1317f3ec45c5fdc6 + pristine_git_object: 8561f5a4e0c0ba1a6a51af8a42713d89b9ba40e9 docs/models/list-plans-attach-action.md: id: d5b7f6e0bc00 - last_write_checksum: sha1:15a5241965f68db426a82537358beb64cad4cd2a - pristine_git_object: 51288bb87c4d1375aa9931b702b721e079b8b7b0 + last_write_checksum: sha1:06d3b009d198986ac24d7c78fc95fbf690a4f488 + pristine_git_object: 93e4587446540b93ffbe0098d3b3f13742c2c596 docs/models/list-plans-billing-method.md: id: 925f450419aa - last_write_checksum: sha1:6b7a4aa06733643802a7ca7e0f6fff763ca86eaa - pristine_git_object: 0466e6a0248bc7e481df111bed7f2db6e27c1e7e + last_write_checksum: sha1:f21829d201a1c91e6ca49463665829e541673d1d + pristine_git_object: faaec3e2653fb569cf621ced508c9b9d163f81a5 docs/models/list-plans-credit-schema.md: id: 3f2d4bd971cc last_write_checksum: sha1:11870331e68516a49daa39eab1dc751ee0a8674b @@ -1368,16 +1428,16 @@ trackedFiles: pristine_git_object: 1781568825934493fdaa174ef979367fd8ceb960 docs/models/list-plans-duration-type.md: id: bc63d2df769a - last_write_checksum: sha1:45a173ddde00f7fdd452a101cf08c6da37158df0 - pristine_git_object: 0bb25ebb30782b3165b8636755729ace8c852c09 + last_write_checksum: sha1:776412644be4783cd7fa9929c44f47cea49d2c03 + pristine_git_object: 24872dad4da39ca8c862e2629fece4bdfcf036b7 docs/models/list-plans-env.md: id: ef9fefbeb4f3 - last_write_checksum: sha1:f1c7dc0b446382318c24004d12f251764b6e0157 - pristine_git_object: bf77ecb66d9c8252e726d98fed90da940edefac6 + last_write_checksum: sha1:5ae97a1a4b7cd7b5c28393370fc70b0a3ad0f9a1 + pristine_git_object: b96f9cd95d8b53e7861771dd649b07607ba5f270 docs/models/list-plans-expiry-duration-type.md: id: 402de479f254 - last_write_checksum: sha1:6ce713141a2a8ad9f973f483239e5a2f1fff5446 - pristine_git_object: f46195e1be2d173f4f413cf5f297675dc5ba184c + last_write_checksum: sha1:44dd4a40c1454a441e2ab569f57dab51dd13f77a + pristine_git_object: 4844fa2eabe6067b1145b9f25cb1a9ba38eeb3d2 docs/models/list-plans-feature-display.md: id: d27baf90b440 last_write_checksum: sha1:883be8ae59b1164c14ff3f905365186ed4fdbc38 @@ -1420,20 +1480,20 @@ trackedFiles: pristine_git_object: 151bd7cef491657c4abca9d42008db6e3c7f0906 docs/models/list-plans-price-interval.md: id: 4fa8aef0b379 - last_write_checksum: sha1:c5feb012c9ec8e2e24914c8878f55b56c8dd0557 - pristine_git_object: 93cbd870f42f2b7f5b37d4d3d434b8d879bc51e5 + last_write_checksum: sha1:72d2e73954e84b0dd48e4a4af9a618d2cd81ab8f + pristine_git_object: 30ff6e6a966cd85f9754f0cccb2ccaba7b033208 docs/models/list-plans-price-item-interval.md: id: d61bea75abb4 - last_write_checksum: sha1:f8f5ef795a7a71897bff8d7e2fb39d280fbecb9e - pristine_git_object: dad8bb2a8ce0bc424ebe4417780bae3f2a0185d1 + last_write_checksum: sha1:c2fef21a913012ccdc85cda5c744f5118311af5b + pristine_git_object: b6fb74fa26de953985e49d79fd64d7400cf72aee docs/models/list-plans-price.md: id: 0ff7fddb4c5c last_write_checksum: sha1:ac808a57822330e7ae85f89c01baef6f8b71daba pristine_git_object: 221e8ac6b0ee5624f8a778162a56f0982458262a docs/models/list-plans-reset-interval.md: id: a8239738ef9c - last_write_checksum: sha1:d5a8b51e7b8b6a3792a21dd16cb36d3b3957ee04 - pristine_git_object: dcdaf47f0ca861b2b52f6458698b8bdf6488655d + last_write_checksum: sha1:6105403c713550f4d3d1c6a785e2fece51fc1454 + pristine_git_object: c6b71b112f78677e6051a95cb8da7b60007fc690 docs/models/list-plans-reset.md: id: 73e6ea5c00e7 last_write_checksum: sha1:21018da5956ea86b823cae081af9a99c1400a8ad @@ -1448,16 +1508,16 @@ trackedFiles: pristine_git_object: 760b01c5522a773685658074ba52e25bfbad02f9 docs/models/list-plans-status.md: id: b9f9a3b52ac5 - last_write_checksum: sha1:55cb11b9ddb0d3bb84effcefda0112a6fab859c3 - pristine_git_object: ca021a4fac21f62f99ca1fd50c7c284720e57caa + last_write_checksum: sha1:86b03c7db83295b5a6db46dca44d0b0bdfe55cb3 + pristine_git_object: 2be2e9d1ca17a61443aaf12ef416463668254df4 docs/models/list-plans-tier-behavior.md: id: 3e6b30e1aad3 - last_write_checksum: sha1:9f8478155d4f98a1156bc3364baf32b69d128e9a - pristine_git_object: 924fead782b1c352ecca3fe8980e36c0fd22ebd7 + last_write_checksum: sha1:d429823d060ba77f2ea07699a45bb00883b73c15 + pristine_git_object: 077a40dfdaac9037f7ecf4ed5a659165a709b8b8 docs/models/list-plans-type.md: id: 902fdb41bbb2 - last_write_checksum: sha1:d193eb535e717935365e99a4eac285d50cfd0288 - pristine_git_object: 8f2779e720e50be967f02698e4e5aa8b89b05e2e + last_write_checksum: sha1:8a14d90ad1601b2d21551520d37af9a93fe51db1 + pristine_git_object: 4ea81aa954edc194f40ee68038b0d4f578c04ef8 docs/models/multi-attach-attach-discount.md: id: 3536a21c4f47 last_write_checksum: sha1:2dfea27b10b93fd68105a4ca58a94f4d4bcf1c1b @@ -1468,16 +1528,16 @@ trackedFiles: pristine_git_object: 199a981d4f69a2401e27f70c412501d66e314cde docs/models/multi-attach-billing-controls.md: id: a2377cae5b76 - last_write_checksum: sha1:4b73860a51e4b96dc97914665f3ee55c2ed14866 - pristine_git_object: 5159b5792907d9ff6ddbbbab7624257c1c133d17 + last_write_checksum: sha1:b4ec8e7c64ab19e45b8d00ea47eec354a5d794dd + pristine_git_object: 31dd439bc05f5aa2f1a68de8d502630bd0a0103c docs/models/multi-attach-billing-method.md: id: 0a1702e9b84b last_write_checksum: sha1:9d863ab54bef7b8b74274b98275c70565f5473cd pristine_git_object: 9f2a0075d180395846c3983ea3d08951a26c1b1f docs/models/multi-attach-code.md: id: 09921b9ac8e1 - last_write_checksum: sha1:671b0456d6419384e67921a9f9a732ffc09a5b97 - pristine_git_object: 8a4eaa5b71c739b2f9ae755205fec9401fb1a70f + last_write_checksum: sha1:ae1135e9ef6470f2e322b2ecf00fcabec0462413 + pristine_git_object: f02c727931ffa9829ba35f4258b5d236c78ebd4c docs/models/multi-attach-customize.md: id: aedf94cc14eb last_write_checksum: sha1:e691f0270d87b69a45e6e7422bbf55802f798479 @@ -1578,6 +1638,10 @@ trackedFiles: id: 1ccfbf701bab last_write_checksum: sha1:77f6a60f64877d9c7ac5906bc43fb6b015fce7a3 pristine_git_object: be79c8fcbc9f81c5151e23308ebbca464ab73bf8 + docs/models/multi-attach-threshold-type.md: + id: f02f8c74ce7d + last_write_checksum: sha1:dd09c346315fab084199517d3fe0994e28bc9476 + pristine_git_object: 4c926b87db17071084edfdb0e08f2e3bb1b0a0c7 docs/models/multi-attach-tier-behavior.md: id: 6e6fb0c88a8f last_write_checksum: sha1:886a4f6a6e04c4caf254f6a0917a2eb46b3c552f @@ -1590,6 +1654,10 @@ trackedFiles: id: 9cc63135019b last_write_checksum: sha1:cb16b10293c7cd3917ccb6555fa95bc3c18ac65c pristine_git_object: 6c21843fd4846a9fb1ea645b25a94269f564008e + docs/models/multi-attach-usage-alert.md: + id: 7d49a7ca51a8 + last_write_checksum: sha1:ed3aedaa8fedcc7a80d8e5b2a57daadaf91fdf66 + pristine_git_object: 13296d7142996405188e7ce0dc78a01e51556e59 docs/models/open-customer-portal-globals.md: id: e0e501393359 last_write_checksum: sha1:eb5269be19a6426dfb429110bd750f61a9e2495d @@ -1604,20 +1672,20 @@ trackedFiles: pristine_git_object: 160d8340897a8bb4ddc8999bd05e49e2baf3c0ec docs/models/plan-billing-method.md: id: 4789ae90ae01 - last_write_checksum: sha1:7e999cb8f855e8f3d917a2024ed3fd88803b45cd - pristine_git_object: aa533c8b3c984f8773a81063ee748107b2eff98d + last_write_checksum: sha1:034ade6e914f7441adf1683c0a9a725bb3ed0234 + pristine_git_object: 6a3e64020748ca7ae31dde83e60228393c659da5 docs/models/plan-credit-schema.md: id: 4a323c1dbf0a last_write_checksum: sha1:167acec310caca3e8e3b1cd5461c5453994f09c9 pristine_git_object: 9dfd58042a96b6de19c6f5f3a21319e1806a35a0 docs/models/plan-duration-type.md: id: 522f79a4e1c8 - last_write_checksum: sha1:abc4c07df7482ae02965a688a956fbb8ec78f476 - pristine_git_object: 304fef3c1f5ee4d7dc24c98227f9e83b1ac6aa65 + last_write_checksum: sha1:1e4df37111b655c409a222f584889f794630e9df + pristine_git_object: 5ca43e03701cb24d900c6fd812a5a140be6a1039 docs/models/plan-env.md: id: 2684553fa0b2 - last_write_checksum: sha1:93e915bc71fe7ba3a8838bc659f1dee939f811e5 - pristine_git_object: 224f3c0569379464c201563e5e5046791ec50860 + last_write_checksum: sha1:8dabc92b19ee301cffbe36bd04e57f33fe05b078 + pristine_git_object: c373f26a00337e22d7530eb9dd99c78bd2970b11 docs/models/plan-feature-display.md: id: 4a23e75025f8 last_write_checksum: sha1:bf383e336435e84f0c51cbf12ad34db39fd5e8cd @@ -1640,20 +1708,20 @@ trackedFiles: pristine_git_object: 61d0d0140fdca02b6fb7b1767e44a57789ff970e docs/models/plan-price-interval.md: id: f4940ecda555 - last_write_checksum: sha1:86d2346e9f97589d88982142e5a8905cb490c68e - pristine_git_object: ee90166f582e2f298d9ae7fc6a7116d030d4c4bc + last_write_checksum: sha1:74cc6484180203f73664a7b151f5b49fb56b9072 + pristine_git_object: ce8ac898fd976c7e213cbfa7a95ec0e25b5cf761 docs/models/plan-price-item-interval.md: id: 2c196a6cec70 - last_write_checksum: sha1:e05edb29e63ef3326bd0c7508c50d59c5157c7dd - pristine_git_object: 09214f5b39fb6f6413144996b8e36afc4b332cf5 + last_write_checksum: sha1:6eb92ba0d4c53b5c2942552ad33c4c8a585a51eb + pristine_git_object: 630586b26f677b90d85d37ff688c8ac8d3b65775 docs/models/plan-price.md: id: f24c0778a8d1 last_write_checksum: sha1:0ae6e5e8e0293c24c92e93d0b98462627958bbbc pristine_git_object: 6a5f8089f942a4a783d148d7a1635970810bda73 docs/models/plan-reset-interval.md: id: 3ae21ed47961 - last_write_checksum: sha1:2647a361fd4c09e1d40acf1343d1c7254106d635 - pristine_git_object: b33f9090de57babc4885223099140d50825103cb + last_write_checksum: sha1:3707cc1f199c2c5138f1575d9d2d451648083761 + pristine_git_object: 5b8b97ba7166041db705ba0b92320fc2edafd78d docs/models/plan-reset.md: id: 1984f6727dbd last_write_checksum: sha1:20b04531084a0f459a1e1f32759dc647571cdb17 @@ -1664,16 +1732,16 @@ trackedFiles: pristine_git_object: ce6551a619ecf231881a602e7eff3f19a0df7d76 docs/models/plan-status.md: id: 3be17dca36b3 - last_write_checksum: sha1:fe2d18a5114bcc66f1f598d7930381a199e1488f - pristine_git_object: c201b3b3faa9c9f9e6ca5e6857d43ddad6b9fc00 + last_write_checksum: sha1:6df376b0847c418721c5ae272172228163302058 + pristine_git_object: 65984d328b576f9d05b489061c52cd7536f98a91 docs/models/plan-tier-behavior.md: id: 460cbe1aaa95 - last_write_checksum: sha1:632bab29fca02e35bbfc910cc2ca767e5245bb97 - pristine_git_object: 2b00459690aac0d23f912cc3408e1aea3784b8b4 + last_write_checksum: sha1:e2a361ce89641eda51df68d8b4149ec17d0c7354 + pristine_git_object: eccc4d94d4a1ac29ff970f7e706e9f8b94611cfb docs/models/plan-type.md: id: fcf04293a720 - last_write_checksum: sha1:ecc184cb1098804970168b1cc83516a04107f2ff - pristine_git_object: 7bc7590d5b2edd57dc26811f4a9ce9e60d4c02a3 + last_write_checksum: sha1:8a55b776c10325c521c94693c2301c3d35865204 + pristine_git_object: e573a1e61915a219fc68d1e31dd55c795dda4253 docs/models/plan.md: id: 900c4149ef4b last_write_checksum: sha1:62a2df64c93f1320fb1a1175222770ede624008b @@ -1736,8 +1804,8 @@ trackedFiles: pristine_git_object: f556d0824e0231f0b3f80a9a24e033cb7f32bcb7 docs/models/preview-attach-incoming.md: id: 562e0f8189a4 - last_write_checksum: sha1:fdd8b435c79ef32de421b73230c664e226c68036 - pristine_git_object: eb39be1e520b9fedda1639bbc2c5766970925dc3 + last_write_checksum: sha1:eb23a93c214d9ff39f5381c4dbaf3167132dc2d8 + pristine_git_object: af005b7366d908ba80f2f038ce0b5e834a4b40cd docs/models/preview-attach-invoice-mode.md: id: 52be741aaa35 last_write_checksum: sha1:aa2e16f83e6f12f781c200d6c0e39775d8c8184e @@ -1784,8 +1852,8 @@ trackedFiles: pristine_git_object: b6b9cab821c830095b2190d60b301f6798865718 docs/models/preview-attach-outgoing.md: id: fd5a276a9cd0 - last_write_checksum: sha1:aefbfe5be8ccd263cc036297d32f3af2477365c2 - pristine_git_object: ada9e4eea5f40d216c2ba31f87dc84d986af484c + last_write_checksum: sha1:9c77d7ae64f21914ac627b1a5e54c2abe26228fd + pristine_git_object: 5d210625047227aeae00e394f0bf104ae18de8d0 docs/models/preview-attach-params.md: id: 29fbf5be911d last_write_checksum: sha1:cb5be461a991a5258fc79f140a9c161214232f21 @@ -1828,8 +1896,8 @@ trackedFiles: pristine_git_object: 38738348cb8b3cb3f1466f4628e7c049ac8bc0a9 docs/models/preview-attach-response.md: id: f53481e3c4e9 - last_write_checksum: sha1:e693056e9671c707e57ba01a35601d8fa950be30 - pristine_git_object: 1c281ef2ecb3cb2b8832c9372569c79cc88f201c + last_write_checksum: sha1:f2597a87350bc64b10ef0b87ed88bfae05cbc107 + pristine_git_object: c6f5e8ee502064aa1152e9d66694021bc13aae49 docs/models/preview-attach-rollover.md: id: 45bd31144856 last_write_checksum: sha1:3a2f09b2565a522e050a728c885e822640ba0793 @@ -1864,8 +1932,8 @@ trackedFiles: pristine_git_object: 2c4b6c1f70e2eae9891821e6b5a1b34b1d9c64ad docs/models/preview-multi-attach-billing-controls.md: id: ce0ddedc67d0 - last_write_checksum: sha1:6c4ba82b4794253fa54d772f8a939d466a871461 - pristine_git_object: fc83158b3f5dc4b42a9069d2252ce823258fe138 + last_write_checksum: sha1:662c9d201162d19407d1f5d89011ba01602af7c8 + pristine_git_object: fc3ecbc9f8cb7a354eadf7c832f860d847ff4baa docs/models/preview-multi-attach-billing-method.md: id: 08a032b6436d last_write_checksum: sha1:8d37dcbdb16b45192fd041922ebd564c88e67b22 @@ -1904,8 +1972,8 @@ trackedFiles: pristine_git_object: bcc5d2e3c58e4cd2a2b7b07611e0653310d311dd docs/models/preview-multi-attach-incoming.md: id: 67ec83daecf9 - last_write_checksum: sha1:3781c843dac5d4b95b75f068a9773607e1ff8364 - pristine_git_object: 5dfcf9aff39f776d7d25560c47c99bc893cc0587 + last_write_checksum: sha1:ca66e7261025125291b058a837e464ab5928f9cb + pristine_git_object: 17dafbdca5489aff0889dca523fcb2cc94c43d6f docs/models/preview-multi-attach-invoice-mode.md: id: 3b06c211aa91 last_write_checksum: sha1:409db019762a74dba3c766d3b08e1a26f0914862 @@ -1952,8 +2020,8 @@ trackedFiles: pristine_git_object: b61c270542dd2b5d76397db487c3cdca174dc711 docs/models/preview-multi-attach-outgoing.md: id: 3291c58c5e86 - last_write_checksum: sha1:8a51956dc0c90db5a39ebe673281917303b439e6 - pristine_git_object: 6a8d7ab42440d8c85dd86ed913ec446b9615d058 + last_write_checksum: sha1:051fe779cbe6680d22b398c8c289ee95a8bdfaab + pristine_git_object: dc4a6ec8561e62c5f0808be788def7ee47efa814 docs/models/preview-multi-attach-params.md: id: 2d749929bd2d last_write_checksum: sha1:90f5ba02b2b4b815f8681f9fbc3fe980c63775b2 @@ -2006,6 +2074,10 @@ trackedFiles: id: ee69bccee8e0 last_write_checksum: sha1:740972361487a9ec8a73bc7b9553af4ffd6de9ea pristine_git_object: d8c311db6ef8f0def941b2f2384361dd4b48a191 + docs/models/preview-multi-attach-threshold-type.md: + id: 0319e15430f8 + last_write_checksum: sha1:c7d57f71acac72e95671508bd44b8c74e1d60ff2 + pristine_git_object: edf6d7291ba7c2a08148702f839f10b81ccff988 docs/models/preview-multi-attach-tier-behavior.md: id: 97ebd79496a2 last_write_checksum: sha1:f4d075f16924dc8196791acb7b2091702dbcd3d5 @@ -2018,6 +2090,10 @@ trackedFiles: id: aeaac9479091 last_write_checksum: sha1:ccfac3d8e2e623664f029fab1511d5d037676c6b pristine_git_object: 3e72823c679b396e06dd5a48d7f9e7d316a5c3bf + docs/models/preview-multi-attach-usage-alert.md: + id: d659a887aee8 + last_write_checksum: sha1:275161f8609f7587105f372e36d9779917f866d4 + pristine_git_object: 01d81b9f3e93ad897d5f12c8c879619fb7402aad docs/models/preview-multi-attach-usage-line-item-period.md: id: c38dc1d62a80 last_write_checksum: sha1:55002b0386a5d59a75e8dccbd201b96ca0618214 @@ -2072,8 +2148,8 @@ trackedFiles: pristine_git_object: 633fc298873b231e618d89bbeb2088ceb01a5a68 docs/models/preview-update-incoming.md: id: 991c5ea1f669 - last_write_checksum: sha1:4ea8e93296ed184a3d37b8f10e25ac8090392d7d - pristine_git_object: d6d34fd50361146c720bf19de335ee0d14a02a61 + last_write_checksum: sha1:920caaafd7129726e8e7bcaebcbd87e6a15f770a + pristine_git_object: f859f35f34578778e891fcd4ea8554bfd2e56aff docs/models/preview-update-invoice-mode.md: id: 50a22a368768 last_write_checksum: sha1:2753fa2eb31780aac9b3daff7744a571c290e59b @@ -2120,12 +2196,12 @@ trackedFiles: pristine_git_object: 0591d40c031e2efeb191782bc160aa17b6dc907f docs/models/preview-update-outgoing.md: id: 8bb066fbd6a3 - last_write_checksum: sha1:4d3ef1f7cbf2fb38be69112a7b89d0b2ae3f569a - pristine_git_object: f444eba07e8cdfaedf59304e3cf95e8e4d7fa9f2 + last_write_checksum: sha1:31ad3231ff396cc7b260cfa5de81c348e1786776 + pristine_git_object: 1d78c98b62d0a1101185567d854e18d93240442e docs/models/preview-update-params.md: id: 4f27fa514996 - last_write_checksum: sha1:15937d783cb2729b3f14cc5652e7f9a0e560f2e3 - pristine_git_object: 730780d43f4a4eb20189e54214bb4fea178cf07d + last_write_checksum: sha1:e32b8ca9b263070cee4028947265d72c9f6e3d44 + pristine_git_object: 96e1493a36a5bbc302f4275353f07fbaa7395bda docs/models/preview-update-plan-item.md: id: d9ec244374c8 last_write_checksum: sha1:4e0823c4d2cc23bbeffa8e42fbcb42a96deb4f30 @@ -2146,6 +2222,10 @@ trackedFiles: id: 096020425f5f last_write_checksum: sha1:2407ee797ec36dc229115a98eb9470cee8597854 pristine_git_object: 050ae04104ddf95dfa6d802d9caa132db36ed821 + docs/models/preview-update-recalculate-balances.md: + id: 8492e617571a + last_write_checksum: sha1:b42eaaf40821a806f22d0a5be11a543d1ef12ce2 + pristine_git_object: 5ca85a603e9526f6e3ec0f6100f85c164c611874 docs/models/preview-update-redirect-mode.md: id: fe04ef2c1f3c last_write_checksum: sha1:d13c5367cd6b1855e85e7387f02004fb776ad153 @@ -2160,8 +2240,8 @@ trackedFiles: pristine_git_object: 0765a516dacc6291b302c98a17bd62753c075619 docs/models/preview-update-response.md: id: 8106134ab3a8 - last_write_checksum: sha1:f73bce7805f9b8ffba02d77795afed780d69e0bd - pristine_git_object: d8648dc94f571a8469c916e9336c1c638552a50e + last_write_checksum: sha1:bd5cf16e816920435e2ab4b0865abf04dbcaf164 + pristine_git_object: a5409878ae000c600310db7b05dcdaf12feb013f docs/models/preview-update-rollover.md: id: fd27620d33af last_write_checksum: sha1:c02354fc996937d2d71acbf4c6938a20089ce1f1 @@ -2196,12 +2276,12 @@ trackedFiles: pristine_git_object: 71aafeb419a55dced06ff025e7aa4ed9af46b812 docs/models/product-scenario.md: id: 53b34e452304 - last_write_checksum: sha1:38c04a7b7c9721934c6b5ad8b0e4f6a286b5e3b1 - pristine_git_object: 2bab4761cb499c25cd0d8edbf482160df0c42c58 + last_write_checksum: sha1:1b89a6c5aca71e11bc1c602eb29f5d7a5d62cdd8 + pristine_git_object: d7854faec6b4d21eab3873c1bfedc4c321695195 docs/models/product-type.md: id: 739b492c48c8 - last_write_checksum: sha1:b16bb5e2b258d3eabb0213f6dd24c5b36a5fc581 - pristine_git_object: 42b3d7af1ce89e608a6c6c41c65098305b5ac826 + last_write_checksum: sha1:deb88681ef554ce1be6e025796ed1b69fdac692b + pristine_git_object: 3545a7deb0f2c9974c512988e31f2b5e6e2d038c docs/models/product.md: id: c91436bbe13a last_write_checksum: sha1:4f32659bdbdd6cc02f2e3664709c4ff969def976 @@ -2236,20 +2316,20 @@ trackedFiles: pristine_git_object: 6133ec7c2af5271b41ce09dddfb0eb8cbeb14418 docs/models/rewards-type.md: id: c48adeacd4fc - last_write_checksum: sha1:27496e9a4a8de62630ebfc6ed7f0a39dd2e3845b - pristine_git_object: 316e1ae6d5e72783cd07bfbdea163f2f7db54806 + last_write_checksum: sha1:fc8c460d22c515495d54385f2b0ea1f045605e26 + pristine_git_object: ca2e4f7e35d62a3bfc3658e8c3e20dbdc6e0ed70 docs/models/rewards.md: id: 4551c882db3e last_write_checksum: sha1:400f48d09c30265fc4650ca42db90109dfdda599 pristine_git_object: 08b89a9cc150d2d174408e0ccb7d2eea9b8e4014 docs/models/rollover-duration.md: id: 2d83c1287827 - last_write_checksum: sha1:b280c07fab2d26e111a692faf91a391869e92f11 - pristine_git_object: c23b8dcea93beed758447fb48b614298922aadf0 + last_write_checksum: sha1:6e5d3704ee11b7ed69c26f2ac40524dba5f3817f + pristine_git_object: 4f9293c3e3617e496703fb7dc28a6947b8b03ff0 docs/models/scenario.md: id: e3aad8ab5efa - last_write_checksum: sha1:092442eb1ef3d6258528e50ee796c1f2dc6ccdeb - pristine_git_object: d237cbd24dcea87d28d5fe57feeeda9244676f66 + last_write_checksum: sha1:dea955e2e6537c03af820669075ace61a6d283be + pristine_git_object: 9d59212541bcf67d5332b474aaa48085a4be19a5 docs/models/security.md: id: 452e4d4eb67a last_write_checksum: sha1:d2af82412a97b139d12d416d11a235638001243b @@ -2424,12 +2504,12 @@ trackedFiles: pristine_git_object: e90fd7ed6b459e1a9cad0d55b8adacc0caf28633 docs/models/update-customer-billing-controls-request.md: id: c32b379ebbc0 - last_write_checksum: sha1:0a140aa486a66a84b04bb77702b3c0b4a3be3fec - pristine_git_object: 1d4ac04e095f14f853adb7335a5eee1d6574af5e + last_write_checksum: sha1:dd80c67321ab61038e8488ad4d84273b30826ddb + pristine_git_object: 53c53146a39333eb96825a6e7a96540d088839e5 docs/models/update-customer-billing-controls-response.md: id: 8f1c813a9ca8 - last_write_checksum: sha1:b24638d3ddd1c532a2a38d0ddf070967bf5ef384 - pristine_git_object: 7699dc8d73683b45f383e3b7333681dfbec5301f + last_write_checksum: sha1:531b246b24c390487d12171d7ccca188f991352c + pristine_git_object: 3e7ba07698b275c053690ed4e6cf7d8fd7e45bc3 docs/models/update-customer-credit-schema.md: id: 3cf3ff338501 last_write_checksum: sha1:f9961157a387d1c5befbbb9efa9169d0912eee4f @@ -2440,8 +2520,8 @@ trackedFiles: pristine_git_object: 5844674b03a3affec2c2537f244e462ffe9437cd docs/models/update-customer-env.md: id: d418dfa14402 - last_write_checksum: sha1:bad1f5982c944a85cc03a4f42cd4c133926e9ac5 - pristine_git_object: 49b1e0d4fa22077061818565a4c51e8516e63087 + last_write_checksum: sha1:d325228f92f006cadbaa962906f743d8ba5e0a1e + pristine_git_object: 43114cf33fd88602a5997e6575e4a30af88001e8 docs/models/update-customer-feature.md: id: 168e1b65d4aa last_write_checksum: sha1:138fd6e774ad7c62ea6f214715b6f723ff26860b @@ -2460,8 +2540,8 @@ trackedFiles: pristine_git_object: e44fe97352c3cda9c0b20cddbca6c5b6139d6a1b docs/models/update-customer-interval-response.md: id: a9d0d34e312f - last_write_checksum: sha1:28df18fdbce1e098643e8ab5ef8be5ab1a0a6911 - pristine_git_object: 45a09bc6f17d2c578073316310a1634ec289bbb7 + last_write_checksum: sha1:c47d9f13238533be2d23995e16b03cd01d6f9a3c + pristine_git_object: fe6c9a746987de0bf0fb25e908e562865e99ed3b docs/models/update-customer-params.md: id: b0eaa77673a7 last_write_checksum: sha1:819c5567450b6fe64775b29b0af21e41626c1852 @@ -2492,24 +2572,40 @@ trackedFiles: pristine_git_object: 7fec85c333456100c9d53f880df4d93bf74ce9a0 docs/models/update-customer-status.md: id: 37adfd15b5dc - last_write_checksum: sha1:c1d49cd41fc2323c5e7b22807963efa958f9a4f9 - pristine_git_object: 3effcadf96576c9a9918ff1ec7329d28cede6dcc + last_write_checksum: sha1:ccf057de69e42a6f304fae96cce81b4829a5c931 + pristine_git_object: 666616765c3b7e7b81855d3d055c6c497b157973 docs/models/update-customer-subscription.md: id: "412049526229" last_write_checksum: sha1:14b670d3f09bd42b9288c24e8ddc440b5e2692ae pristine_git_object: 61190d55755f7d4d25d433defd47bc3ed6a0b3a4 + docs/models/update-customer-threshold-type-request-body.md: + id: c38d53770bd0 + last_write_checksum: sha1:cf290b4b827ef73b9ebb30c9450c9e09fa237e37 + pristine_git_object: c49227627cc71886b1cdfc42753ba5950f0d3792 + docs/models/update-customer-threshold-type-response.md: + id: 140d164cb745 + last_write_checksum: sha1:8125ee728d535329fef313b9786cd3c5dd35b31d + pristine_git_object: bed4e9087c3e5352f2fa9d7c146daebad05621fd docs/models/update-customer-type.md: id: 875a45ee72f2 - last_write_checksum: sha1:044907040da1e827528a5a25f99df920f9ffcfbc - pristine_git_object: 83d707b139180304ae75f646872142d39330a358 + last_write_checksum: sha1:fdca10efc943fd7b923fef01c574d191771c2fa0 + pristine_git_object: 62ccf1f0cd28011a65cd6018fc2ff36321e84329 + docs/models/update-customer-usage-alert-request-body.md: + id: c4f5132a2774 + last_write_checksum: sha1:a5f472194a56dedf903b4db196cf02d9accdb2a7 + pristine_git_object: 37e958f7a497670ca6bf45ee5be4aa6c6a7aa8fa + docs/models/update-customer-usage-alert-response.md: + id: 0c424e14021b + last_write_checksum: sha1:a4b3c268d269b40e744ed8b47585cff1bfa8795f + pristine_git_object: 1d8bb52999352d17fba0f1dd014a5508f813e369 docs/models/update-entity-billing-controls-request.md: id: 9b1761390078 - last_write_checksum: sha1:e5f4ac7cd42c1701b8c2da536dcf2555f7a7acdf - pristine_git_object: 1218a68accf2a2b433d3878b214543809d77fce3 + last_write_checksum: sha1:8440aecb90ce47726eea9c1b4ebf5d052f73ccca + pristine_git_object: 808191209a3d0eeda7820f3452eb0c9e9270768e docs/models/update-entity-billing-controls-response.md: id: 293e6deae7f0 - last_write_checksum: sha1:d9df4288b6089edf6c8817292590574152613fb0 - pristine_git_object: 153262087f4bed3c8b7895c4cf276c7aa4076167 + last_write_checksum: sha1:1c659a6b41f6fa0015309788c9f2882d1ade7515 + pristine_git_object: 193d6273870de4ced9438cd6cb62c53a166bd1f8 docs/models/update-entity-credit-schema.md: id: cb9d6a715c8f last_write_checksum: sha1:8c0ce2f7b224153309c9253e02a846d9a456f232 @@ -2520,8 +2616,8 @@ trackedFiles: pristine_git_object: b7c9ab7b072af08f005d6bbd73f93b256f6b4ff3 docs/models/update-entity-env.md: id: ba8bb70ec367 - last_write_checksum: sha1:dfa25fd93c754b0f1b90869007dab9b3e4280d9b - pristine_git_object: f8515ff3068a6db2e14fdf925285e6cd588bd3f5 + last_write_checksum: sha1:ebabe20304aaf868147c89ec93b0fdf8c7410504 + pristine_git_object: 97702406e52bc0e63281c142d436d385a10f6399 docs/models/update-entity-feature.md: id: cdf0e1868d89 last_write_checksum: sha1:4ac2661631f51e41dc56ead085ba1c4f3c18a416 @@ -2560,16 +2656,32 @@ trackedFiles: pristine_git_object: bb4652db59eda761cb9de9c7c753c2d84e20a02d docs/models/update-entity-status.md: id: a123286977c4 - last_write_checksum: sha1:3b260455837c095923e54a862f811e23fc09e75e - pristine_git_object: 0a022e6eb0c5caf8ec0020f2b8778c0ac5f41cbf + last_write_checksum: sha1:2f194c547bd8c6fbb6dc0330bd195f9b3e47f753 + pristine_git_object: 1403f0344b32c4c2b3883a742d6e56444ad63420 docs/models/update-entity-subscription.md: id: 730dc25c840f last_write_checksum: sha1:137d384d2d4751c301eb6c0c09a50f4dee9a4fc9 pristine_git_object: cd975f24e8e7d76b803aeff4d46fc0c22ea780ee + docs/models/update-entity-threshold-type-request-body.md: + id: 87b1d9fc5d5d + last_write_checksum: sha1:2425222f4e2a9ff441ef3ef7b9021ad6bed46066 + pristine_git_object: 579429d97deafbc24575385a1b05ea1b52240ef3 + docs/models/update-entity-threshold-type-response.md: + id: d6e1ad6934ce + last_write_checksum: sha1:bbd117bc25653a26b4b5d4be1e673c225ae554e6 + pristine_git_object: 86a4a04ad98f7da4354b599f0e06b8879e180459 docs/models/update-entity-type.md: id: 394dfb877cb7 - last_write_checksum: sha1:6a66ba39192c4124f1d88a4002968a1375057eff - pristine_git_object: d978e0a8bc80c75a43521c2d1ce78d0a11132270 + last_write_checksum: sha1:da5bfb334d71880215ba0eb97bbc1423017568a7 + pristine_git_object: ab98d85bab5f617228a12b1cb88bc7abe4bff902 + docs/models/update-entity-usage-alert-request-body.md: + id: 327b3aeb824e + last_write_checksum: sha1:d6701450fc5e8be33295725bc01b79a1af1577c2 + pristine_git_object: c1d0410988665ee5b445548cf904f36f29d2ff31 + docs/models/update-entity-usage-alert-response.md: + id: f176e3c4b1f2 + last_write_checksum: sha1:9b82bb47d5f5283a9e04a545c24718158e4790ba + pristine_git_object: 02c8d82fdd25dfcd57462f0ec958617109fa8795 docs/models/update-feature-credit-schema-request.md: id: a82e108a3756 last_write_checksum: sha1:9aeca0b0271a2657e571cda1a2182ba633d8efff @@ -2604,12 +2716,12 @@ trackedFiles: pristine_git_object: 011cd5876e919cdf84167f388a8e576b2ff96e50 docs/models/update-feature-type-response.md: id: 55ef8f61ed28 - last_write_checksum: sha1:36bc60e36ec75ab72b26645751fe8e4a02437053 - pristine_git_object: 8feb59b6f2386611f2db04d66bf8d95202e1d9ec + last_write_checksum: sha1:8edc60f47c6feaabb96b01479306ce762c3b863c + pristine_git_object: e537c89a174c185a0833eb63be1e07d8a0558325 docs/models/update-plan-attach-action.md: id: 3f596f5740b1 - last_write_checksum: sha1:02f0e4cb31d8b2cd6ba5d6a38b16d10dcada44fb - pristine_git_object: e67655ce96d6c0b48a0a6930027a0394b816136d + last_write_checksum: sha1:e58f8b39293307e6bb75b35e03d6cf3a459d7c2c + pristine_git_object: 073fd9c8dd01dbda27bef5f28da8ec408764c889 docs/models/update-plan-base-price.md: id: 7f764af15f7d last_write_checksum: sha1:342436cc671fbfa0fa02779f5635f2ae28804626 @@ -2620,8 +2732,8 @@ trackedFiles: pristine_git_object: 3fcfc1ffd0e051ca7d6267202af8ac6756a48f0e docs/models/update-plan-billing-method-response.md: id: ddca1b594754 - last_write_checksum: sha1:684b8ac067ff95bd19c8252fed27767389012afc - pristine_git_object: 2f38389e4cc43531f126c82a08713edf787e877f + last_write_checksum: sha1:045532224fb1a560ae04aa85cfbc175a5208ca9e + pristine_git_object: 1c3d7ae7c5e8f54333c14c3e480eefdd7b4b1ebf docs/models/update-plan-credit-schema.md: id: acaf39d42386 last_write_checksum: sha1:af3af2720a587f377efbf14df3e586cc376ead26 @@ -2636,20 +2748,20 @@ trackedFiles: pristine_git_object: 823ca3a08fbfe410bcc170abba6a9fb1a26aa848 docs/models/update-plan-duration-type-response.md: id: 7e8e1a2d27c1 - last_write_checksum: sha1:45fa868e2ea230807bb93b96cf1d1d4d091db40a - pristine_git_object: e2aad42fd702d3b0a19fb4b3766dd26f60f20891 + last_write_checksum: sha1:ed21c15df2b5c41affc78e28440b4e3051f7e0a2 + pristine_git_object: 1a60b6ee9910560a58e9dc1ee2413465b6f0b724 docs/models/update-plan-env.md: id: 1ec7258b0baa - last_write_checksum: sha1:d4594b9ca68ab42976728a90051e63158147be0b - pristine_git_object: 1a9287acd4e08b4512cfbcdb04682ba80b280089 + last_write_checksum: sha1:5c914a9bfde14e77725e1f7fd80b55c4435ba9b9 + pristine_git_object: 3027bd5dd67aadcabafbd0ebcb694f654fa9a143 docs/models/update-plan-expiry-duration-type-request.md: id: 9deda139c872 last_write_checksum: sha1:ed51270204f6b3b01d4d4f4fc2069debfdddec88 pristine_git_object: 89747b93d89370fa39a0b3a802c255401ab17567 docs/models/update-plan-expiry-duration-type-response.md: id: 4f1456745f6b - last_write_checksum: sha1:804c08642b3c40af78c8f534239f6ec353381ad0 - pristine_git_object: 0d2036c1b49a9dfd90cdc7c10c4770eada3581a3 + last_write_checksum: sha1:037ea11512c88eab6c9db6733344a4a99edbb3ff + pristine_git_object: d886a3142cb7db7666f94de89ad811a3be1a66fa docs/models/update-plan-feature-display.md: id: 60e529fe4eec last_write_checksum: sha1:bcb11546dcfc1e87c3b7e2c99e158441a6da6ac7 @@ -2712,12 +2824,12 @@ trackedFiles: pristine_git_object: 308fdd7c25468a6dd81b07ed46391d1a9bdc0113 docs/models/update-plan-price-interval-response.md: id: 895a33399c7b - last_write_checksum: sha1:cc3a352b3f4c6a155eab5c20297a4d9f423d0d43 - pristine_git_object: e2b7b2f28422ed55186e1d38418b1054e8362b19 + last_write_checksum: sha1:bc84d3d69ff743ef119f7752e6256f3aa01c5cb4 + pristine_git_object: 1bbe591e12e17abd1e6d439f725515c2ad30d4b9 docs/models/update-plan-price-item-interval-response.md: id: e909a35141c8 - last_write_checksum: sha1:ef54f93e6dd1c247b2fd91615e01655f5d090c8d - pristine_git_object: 82e7a008247cf0745ee12616656dc0fc885b482c + last_write_checksum: sha1:cb7187b76af4aaebc38bef1bddef721ca8ec85f8 + pristine_git_object: e3ffa074588d8dc4cc5ce76cbb57b0adcb537228 docs/models/update-plan-price-request.md: id: 4602b3ebb686 last_write_checksum: sha1:4a7faf35e7d8382ee50e8cde87cf15cfc034fe5c @@ -2736,8 +2848,8 @@ trackedFiles: pristine_git_object: d30dc71cbb6362b2817893a7b9d85f600b3fe704 docs/models/update-plan-reset-interval-response.md: id: e04031025a69 - last_write_checksum: sha1:7cf89cfb935aa10335fd3e731b1510b0cb7b124a - pristine_git_object: 39bb94ebcc183905b3d84961f59a6ffe16ef8221 + last_write_checksum: sha1:39f5bdcb899e1855e84edef3ce03c619a7ef8b40 + pristine_git_object: d89b60eb37801f8fa2a3f4653e65c0ef3d356d13 docs/models/update-plan-reset-request.md: id: cc5ffaf44e69 last_write_checksum: sha1:7b5494303638691323b06a24345154f1922c09ae @@ -2760,16 +2872,16 @@ trackedFiles: pristine_git_object: 8bad9b30e3d3445904a7deab4bd769f28a412f44 docs/models/update-plan-status.md: id: f2d8e8969aac - last_write_checksum: sha1:0c7f9b87be7ec7992c7a7cfae8889af100c4b5b2 - pristine_git_object: d8f5473b55509af4d6b3ca9a5100516c32944f61 + last_write_checksum: sha1:1282e2a189f6086805af184a94e387a7daa61dcc + pristine_git_object: f03621514664842542256ec0b1d280a238d72cbd docs/models/update-plan-tier-behavior-request.md: id: 8ce61fb8637e last_write_checksum: sha1:b9e837d1016b53e5ba973bdb3aec0f63a3cd7bc0 pristine_git_object: b027e08b063fed9b87885cae46a9fa282124596c docs/models/update-plan-tier-behavior-response.md: id: 4da31d438a94 - last_write_checksum: sha1:319738e80d8e1648e746fd5b41fde23412eb6a1e - pristine_git_object: 1c7755baaca987e11d366427182be05949c8fbc7 + last_write_checksum: sha1:1db3f4e8189db73c50db0299bb5dca95165f57c7 + pristine_git_object: cd0aa9bf04621edd356628caa97a8c3bcad8395a docs/models/update-plan-tier.md: id: 0cac01f522a2 last_write_checksum: sha1:65a9e826becab3da5c24fef7fbf915992df4dd01 @@ -2780,16 +2892,16 @@ trackedFiles: pristine_git_object: af6af1c81c983d8a50d21b44329a3aa0bd24ebed docs/models/update-plan-type.md: id: 5cfd33c31fa4 - last_write_checksum: sha1:e6925622d28aadf7dda81743c075f806a1889b3e - pristine_git_object: ef8461bb5f5b6dde98c8f325bb176effbe346b88 + last_write_checksum: sha1:dbe46f3341404b4f4da84312247a83a410f26129 + pristine_git_object: 286ef6a9dc6cf9cb52cedb2b6ffb75be463d1d81 docs/models/update-subscription-params.md: id: 2f1abbd42a8a - last_write_checksum: sha1:a9dd736840a5111ffe367a5ffeaee8495e074ad8 - pristine_git_object: bcc2607ea2c7c6d50c1ff2283767fd44af9cebf0 + last_write_checksum: sha1:8d3dec0da8e950d8b884e0c003c48c3bcd104978 + pristine_git_object: 96e2b759e232fb9fa2c603098e6bbc6a72272f91 docs/models/usage-model.md: id: 32a269601e79 - last_write_checksum: sha1:faa6fbdacaf3e836fef0c74a2430790ed5181d55 - pristine_git_object: 14952a4ee67b77ab09f4697e1767649ea412d2d5 + last_write_checksum: sha1:c8bfcbed266005cbcc6114bfb969c2d90d028d1c + pristine_git_object: 56e566eba7aeffa9422da7c6b4bcb80a123e5f14 docs/sdks/autumn/README.md: id: d27c9292a1a3 last_write_checksum: sha1:8b3c26a9a0202d01d704f782172e06caef3a9b64 @@ -2800,8 +2912,8 @@ trackedFiles: pristine_git_object: a366694c3e2f4df91e7ae37aa212d8f07cb83c40 docs/sdks/billing/README.md: id: dc915331dd9d - last_write_checksum: sha1:53dfd365ec6177a94b1406953b4632ad45e556ff - pristine_git_object: 5620ab958f6bd99e45cc350d99bf7c53b461a27f + last_write_checksum: sha1:e7ceda34964ab16d3b58cdcdad503eac1d549a60 + pristine_git_object: d94ecd720f2708a966fb68b01f36fb4cac4a2617 docs/sdks/customers/README.md: id: 9332759cffc2 last_write_checksum: sha1:1eb602635426550359b47163b6f402b6c7b959df @@ -2852,8 +2964,8 @@ trackedFiles: pristine_git_object: 397d986f1533891eddd2b39bf57e502c5d71613a package.json: id: 7030d0b2f71b - last_write_checksum: sha1:c0059fecd0464a94b545d4e9ce4ce57acfd5b151 - pristine_git_object: 0762dd7e393759664d238185cfa34215f13933ef + last_write_checksum: sha1:ccce7e66c9bb241f74bee50fac536d72da80c2f9 + pristine_git_object: cea8a792bbaf1e6c7e05c026273b04c6576f2441 src/core.ts: id: f431fdbcd144 last_write_checksum: sha1:f8f24a3ca09c1efb285d7a75ad3697d0128f47e2 @@ -2896,16 +3008,16 @@ trackedFiles: pristine_git_object: 104a2ea5718c4d0cca3ff3e50f762a2225208b05 src/funcs/billing-preview-update.ts: id: cd3a375787c5 - last_write_checksum: sha1:4f523857caf820f20b7abcc091ce321f55cd0da9 - pristine_git_object: bcbdaddd77b806a1169bf489ef1ed6b77fcea92f + last_write_checksum: sha1:2a22d8b6fe3f2ccd268546dfc6cca878c1e2c1d1 + pristine_git_object: 69d04d5f49f6199719abbd207cccb9a24a27f37a src/funcs/billing-setup-payment.ts: id: 4d2d8096862d last_write_checksum: sha1:8104b79ac9e2922b97ae923454d0e41a24dd9d8f pristine_git_object: e30ea0c1e60bcfce426ff277ce40ed8ef3e77adb src/funcs/billing-update.ts: id: 5c14ddfe1de0 - last_write_checksum: sha1:839ea39066913d0f4134876046d33fe10db2ce27 - pristine_git_object: c7c08f719274c305f96206868b9101704b285b51 + last_write_checksum: sha1:32facdab8b2196ba0ab1a4fe17ae58d4149b43f1 + pristine_git_object: 43bceea02eca1fd4b70b344177986d427e87089e src/funcs/check.ts: id: e962b1e3321b last_write_checksum: sha1:88705e19bfe4ef3397cc66b815ec842590111be0 @@ -3024,8 +3136,8 @@ trackedFiles: pristine_git_object: 44be0eae8246521b230e8e711a88eff738fc015d src/lib/config.ts: id: 320761608fb3 - last_write_checksum: sha1:eb7590ff72d3784a45a7bdc106f510ea9d55fd83 - pristine_git_object: ef0ca20614889d20c6e6b15c538847eb1f768792 + last_write_checksum: sha1:ddc74e2d374d3fe2ee1e8a47b0c762259c05c96f + pristine_git_object: 468ec0db08775aac0b972b73beb5edce16b9a6f5 src/lib/dlv.ts: id: b1988214835a last_write_checksum: sha1:1dd3e3fbb4550c4bf31f5ef997faff355d6f3250 @@ -3040,8 +3152,8 @@ trackedFiles: pristine_git_object: 6439826c5dcc6f5b21cad742cb1b24951d912a6a src/lib/files.ts: id: e5efa54fcb95 - last_write_checksum: sha1:795c14026405d547bfc47012f7ad9666e530fa7f - pristine_git_object: 6ca6b37d35a12448da176ee2517cc036db12c81c + last_write_checksum: sha1:d0f325f445b8d22e7d73baf052c585ec2dd24841 + pristine_git_object: 0344cd046d033f9e8a55de02f7935c5cec5dc30b src/lib/http.ts: id: 63a80782d37e last_write_checksum: sha1:797cbf16d3c7c4d62d3ba0eedb08617524938457 @@ -3056,8 +3168,8 @@ trackedFiles: pristine_git_object: d181f2937d4128afbe9b0327e0f81d1fa9c877b7 src/lib/matchers.ts: id: d54b2253b719 - last_write_checksum: sha1:5338bf281b49894fea64a93d18c207afec547959 - pristine_git_object: 9a72b3fe2b6b7f39be7c7283f85c36b9c874021e + last_write_checksum: sha1:fd1a4f9ab108d1fbc597b60e529ce5144c0982f5 + pristine_git_object: 2f22acee9666d9c16532376b0897cd00608a608b src/lib/primitives.ts: id: 74859f750b28 last_write_checksum: sha1:987869fc54790b9c22e9182099103d6aa7c67016 @@ -3076,8 +3188,8 @@ trackedFiles: pristine_git_object: a39f7e35308f9b6aa4bd787c9cd5dbfd0630064e src/lib/security.ts: id: 0502afa7922e - last_write_checksum: sha1:292e4f6785c5d8ac82a00a5ab7cca593162b70d1 - pristine_git_object: ee8dd58612b10b6e2b5cd17266af7496e611cdaa + last_write_checksum: sha1:2a17865cac9bf5b9703c4dcb294e6e1dcc2c018f + pristine_git_object: 659b39acbbbaa57b369984bf6cd97e1867573501 src/lib/url.ts: id: b0057e24ed76 last_write_checksum: sha1:d124050c7e755c0cce233b9e029afb584ff65201 @@ -3104,8 +3216,8 @@ trackedFiles: pristine_git_object: 0a1fa37762bc3d687bf5305bff7ec1e429e09a94 src/models/billing-update-op.ts: id: e7371769c7ca - last_write_checksum: sha1:4a65baa7b9d0e495c629a320813d59a9a9a928ce - pristine_git_object: 055c273ee0653391ad0e5905e2642bc4a4b17e9a + last_write_checksum: sha1:c290454f78154046bb4805327762a572a61eac13 + pristine_git_object: 32ffe830bae55e19f004738fbf46c94c545ce519 src/models/check-op.ts: id: 42085bda016a last_write_checksum: sha1:29c31308dde34633123443a1c0b206bc2af7cb38 @@ -3116,8 +3228,8 @@ trackedFiles: pristine_git_object: e29ec4d1c6b55865e72c8a2d415bdf19a9b2a8ed src/models/create-entity-op.ts: id: 9ad8367048a1 - last_write_checksum: sha1:fa1a4d327132c3407269e1b0d7c812f5ca77f965 - pristine_git_object: 3e065c21a00783dafd92227d253ef6075af5cbb3 + last_write_checksum: sha1:9fed6cb8c773a6d29dd7e906fb75c7b9d73ce065 + pristine_git_object: 44f5f23e693ed618765cfb59ca83dbbd978b09ef src/models/create-feature-op.ts: id: 06f0161d677b last_write_checksum: sha1:2343ab517f362b3eaaa65e9039cec647ac497111 @@ -3132,12 +3244,12 @@ trackedFiles: pristine_git_object: d979198ac227f8e5731e5ca5e2e34b55d88da348 src/models/customer-data.ts: id: 04dac7ee392e - last_write_checksum: sha1:55cdeb7dbfa50ff55b2394c24c0fc381fcb3483a - pristine_git_object: d519831f23c59f39560262cfd7fddafc297f6d62 + last_write_checksum: sha1:a2b386eb741d2913ac5cc222cf59e7887e1ecb7f + pristine_git_object: 06b6401180e19e3a95b59719948b00ae24bb65ea src/models/customer.ts: id: 20be78c552a4 - last_write_checksum: sha1:438af099cd020e8ca54e91bf11291f0b2ed5ac90 - pristine_git_object: 4d0e407238a5b0c23602450456d7157caf32142f + last_write_checksum: sha1:5add38a9938c0570d84972ab059c6157da1740ff + pristine_git_object: 0196844d6cca018f17d138035e2935beb4e5066a src/models/delete-balance-op.ts: id: ea84d6bda9c3 last_write_checksum: sha1:e592136c1840f2dff9a17d7ed4dc1b6e6bd38f0a @@ -3164,16 +3276,16 @@ trackedFiles: pristine_git_object: e32f582176649899148ca6afba2175c75dc7abdf src/models/get-entity-op.ts: id: 7932a3cea5c1 - last_write_checksum: sha1:420b4e34b466d99847b368614323ce09803e265b - pristine_git_object: 5eba1e01d9a4aabc1ab842ebec97d9fe3d7881d3 + last_write_checksum: sha1:7acc5336f1e1186d55c7967d3c4c995127658d49 + pristine_git_object: ce36a7c97239a9091d3cf4c6b8b718f77d3bdd78 src/models/get-feature-op.ts: id: a820efa3e08d last_write_checksum: sha1:e42d99dba37a2380e1e9ed4afe9b69460598f578 pristine_git_object: 2dab76561c71950e9622b079172e1d0dc339b081 src/models/get-or-create-customer-op.ts: id: 46f8f65a57f2 - last_write_checksum: sha1:35772dd4e5d6f979cfafce29961835483fe904b4 - pristine_git_object: 0d7c5de1ab2264df40705413807685f918367411 + last_write_checksum: sha1:fa54d8e17df6eabe05199184af93ad454c10b55d + pristine_git_object: 67fd8b56b02d968c71df3ad700096be69a25a660 src/models/get-plan-op.ts: id: 91c8f8dda7c8 last_write_checksum: sha1:4ce23c219fc779e598370b220970c90ade2b12f9 @@ -3188,8 +3300,8 @@ trackedFiles: pristine_git_object: 4a4d15fbe91ea4e8d6b053f6b2874f2bb272ee08 src/models/list-customers-op.ts: id: b391692c8429 - last_write_checksum: sha1:257bc3828af7ab5186e9a936ddf7c986ea24b295 - pristine_git_object: ffe79c5317c42623b35792a1466c59fbdc82c2e3 + last_write_checksum: sha1:6e1bcb810bdaf31f92f8dfdb595bd1d349da788e + pristine_git_object: 7c17614447ca43360529b04bf94d6afaa401a1ea src/models/list-events-op.ts: id: 82a9f364bb21 last_write_checksum: sha1:2d409806ca68c98d4a9a5a1031d591f831065c75 @@ -3204,8 +3316,8 @@ trackedFiles: pristine_git_object: 14e70100aa956ccbb2cf296781e15d67dd1a9b6e src/models/multi-attach-op.ts: id: 99a2b77c1afc - last_write_checksum: sha1:1aeeda38d2ef8b43ac2f10f4891690dacc26ac1b - pristine_git_object: 637e66fa77050607a8775dbecd168952835f9819 + last_write_checksum: sha1:7a766c1c932de94611cc82560cecc9e4d0a376c8 + pristine_git_object: e7b0525505667e0d599f5cf08e3154e895b5a550 src/models/open-customer-portal-op.ts: id: a003eb4172a9 last_write_checksum: sha1:5e672fc975a0336c963181042a770c2a43cbc0e3 @@ -3216,16 +3328,16 @@ trackedFiles: pristine_git_object: 57c2119234cc30673a067a26c8ccca1244b2295f src/models/preview-attach-op.ts: id: 3efc6e3443a7 - last_write_checksum: sha1:9d0abed14a2ba8a527da4bce44a3b5edd3f20170 - pristine_git_object: f0a16ec5a87e519b5a56688a9b7d6239a3f683ba + last_write_checksum: sha1:593828c02419a43568b504304fd85ef55c2a5843 + pristine_git_object: 69b603db9c6b7dcafcc1431820c3cc09bd104313 src/models/preview-multi-attach-op.ts: id: e4847dc281a6 - last_write_checksum: sha1:e2b710a11a6963850e08dc85d9aa42a3f2fc2490 - pristine_git_object: 84255104a8b9452190671556d90d1e2cf7278b60 + last_write_checksum: sha1:b81fa93c90e11a02ee71acda6542c262cca0d5c2 + pristine_git_object: 16920a58e0f56d9b268851b14404aade061dfcf3 src/models/preview-update-op.ts: id: fcbbbf3b22ac - last_write_checksum: sha1:a9c0fc7fd86a990a86ca061cc547693d29b3da51 - pristine_git_object: bc3526adf2fa788b7b4030b6f2d7ceed238e115d + last_write_checksum: sha1:76069c26da18000a06ea93d18c265637ed2c87a9 + pristine_git_object: c6884ba5fbac5c488e525042acf0b5285eddd16b src/models/redeem-referral-code-op.ts: id: 511bf73dc4c6 last_write_checksum: sha1:9ab6622018c82175ea98d2b26eadb4abf08f441a @@ -3256,12 +3368,12 @@ trackedFiles: pristine_git_object: bfa2a0d29be530ccbf883a563399b35712623642 src/models/update-customer-op.ts: id: 5d226d30d8e4 - last_write_checksum: sha1:b5b6a0cd4a88537dba83fcd16b27700cafa25c83 - pristine_git_object: bcd66af95d7eb16e9445ea8d2dc9840a61eed206 + last_write_checksum: sha1:4cd61e0c3a7dd54edeba51950baac22544ee1ae9 + pristine_git_object: 64d04d0eea23580ec5efa63c961ea60396017788 src/models/update-entity-op.ts: id: c3fdb6479f02 - last_write_checksum: sha1:4d32ffe520a8bd2395e75ab26387b940b16c8ceb - pristine_git_object: 1761c6366d436d2ea58db62a41bb1f12d5285346 + last_write_checksum: sha1:c2721d95e8f452174a3b8e029eca28613d6070a0 + pristine_git_object: a3974d0fbcfc23bb2df016b3ade6941a0a4b4565 src/models/update-feature-op.ts: id: 7c27d245784e last_write_checksum: sha1:fca4d29e843ff678c6f85258ef40668a46da5e42 @@ -3276,8 +3388,8 @@ trackedFiles: pristine_git_object: 571de419ea3321d79acec4bddbb46b1580007115 src/sdk/billing.ts: id: 10905058c4ad - last_write_checksum: sha1:8bc56dcf1da206ef7e88fb5316e02f71cb37247c - pristine_git_object: 28e980d78d03b1e3eb5943baea72517a1ced0855 + last_write_checksum: sha1:6de4f6f49f746b15fde5f6f0ef89f5442e24487e + pristine_git_object: 7891ff9bce4d17d0fa8675226b696670ab50f39e src/sdk/customers.ts: id: d33e193e0c00 last_write_checksum: sha1:d7bd2dbe3435c37f2abad38c59c64b725a2dac4b @@ -3364,8 +3476,8 @@ trackedFiles: pristine_git_object: b7a2a13f3dff50663429df24ae2f55b647ff1084 tsconfig.json: id: 61ebb9fd6e8c - last_write_checksum: sha1:79dc1550d921fefec69f8632ed3b24dad41ca2f6 - pristine_git_object: 76110f883cac40bb04fb2f376cd69ea8c8d6d215 + last_write_checksum: sha1:c3ec7996536cdb9a54319e7d1eb3b199b733d34c + pristine_git_object: 0ccdbe286a8780e06a469a9eb7e2fb95322fd507 examples: get_/plans: speakeasy-default-get-/plans: @@ -4115,7 +4227,7 @@ examples: application/json: {"customer_id": "cus_123", "plans": [{"plan_id": "pro_plan"}, {"plan_id": "addon_seats", "feature_quantities": [{"feature_id": "seats", "quantity": 5}]}], "redirect_mode": "if_required"} responses: "200": - application/json: {"customer_id": "", "line_items": [{"display_name": "Percival_Towne81", "description": "as unfortunately wherever crest", "subtotal": 1273.44, "total": 7630.04, "plan_id": "", "feature_id": null, "quantity": 1062.24}], "subtotal": 20, "total": 20, "currency": "usd", "incoming": [{"plan_id": "", "feature_quantities": [], "effective_at": 1983.1}], "outgoing": []} + application/json: {"customer_id": "", "line_items": [{"display_name": "Percival_Towne81", "description": "as unfortunately wherever crest", "subtotal": 1273.44, "total": 7630.04, "plan_id": "", "feature_id": null, "quantity": 1062.24}], "subtotal": 20, "total": 20, "currency": "usd", "incoming": [{"plan_id": "", "feature_quantities": [], "effective_at": 1983.1, "canceled_at": 2257.08, "expires_at": 7910.67}], "outgoing": []} multiAttach: speakeasy-default-multi-attach: parameters: diff --git a/packages/sdk/.speakeasy/out.openapi.yaml b/packages/sdk/.speakeasy/out.openapi.yaml index a9be56129..3c55826f5 100644 --- a/packages/sdk/.speakeasy/out.openapi.yaml +++ b/packages/sdk/.speakeasy/out.openapi.yaml @@ -84,6 +84,7 @@ components: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -120,6 +121,35 @@ components: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) title: CustomerData description: Customer details to set when creating a customer @@ -158,6 +188,7 @@ components: enum: - sandbox - live + type: string description: The environment this customer was created in. metadata: type: object @@ -199,6 +230,7 @@ components: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -235,6 +267,35 @@ components: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) subscriptions: type: array @@ -260,6 +321,7 @@ components: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -382,6 +444,7 @@ components: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." consumable: type: boolean @@ -511,6 +574,7 @@ components: enum: - sandbox - live + type: string description: The environment (sandbox/live) required: - id @@ -556,6 +620,7 @@ components: - fixed_discount - free_product - invoice_credits + type: string description: The type of reward discount_value: type: number @@ -565,6 +630,7 @@ components: - one_off - months - forever + type: string description: How long the discount lasts duration_value: anyOf: @@ -757,6 +823,7 @@ components: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -804,6 +871,7 @@ components: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -867,6 +935,7 @@ components: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. interval_count: type: number @@ -893,6 +962,7 @@ components: enum: - graduated - volume + type: string interval: enum: - one_off @@ -901,6 +971,7 @@ components: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: type: number @@ -912,6 +983,7 @@ components: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." max_purchase: anyOf: @@ -949,6 +1021,7 @@ components: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -975,6 +1048,7 @@ components: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -991,6 +1065,7 @@ components: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -1010,6 +1085,7 @@ components: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: type: boolean @@ -1024,6 +1100,7 @@ components: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: - attach_action @@ -1061,6 +1138,7 @@ components: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." consumable: type: boolean @@ -1180,6 +1258,7 @@ components: - quarter - semi_annual - year + type: string - const: multiple description: The reset interval (hour, day, week, month, etc.) or 'multiple' if combined from different intervals. interval_count: @@ -1213,6 +1292,7 @@ components: enum: - graduated - volume + type: string description: "How tiers are applied: graduated (split across bands) or volume (flat rate for the matched tier)." billing_units: type: number @@ -1221,6 +1301,7 @@ components: enum: - prepaid - usage_based + type: string description: Whether usage is prepaid or billed pay-per-use. max_purchase: anyOf: @@ -1426,6 +1507,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -1462,6 +1544,35 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) expand: type: array @@ -1535,6 +1646,7 @@ paths: enum: - active - scheduled + type: string description: Filter by customer product status. Defaults to active and scheduled search: type: string @@ -1588,6 +1700,7 @@ paths: enum: - sandbox - live + type: string description: The environment this customer was created in. metadata: type: object @@ -1629,6 +1742,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -1665,6 +1779,35 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) subscriptions: type: array @@ -1690,6 +1833,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -1812,6 +1956,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." consumable: type: boolean @@ -2104,6 +2249,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -2140,6 +2286,35 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) new_customer_id: $ref: "#/components/schemas/CustomerId" @@ -2191,6 +2366,7 @@ paths: enum: - sandbox - live + type: string description: The environment this customer was created in. metadata: type: object @@ -2232,6 +2408,7 @@ paths: - day - week - month + type: string description: The time interval for the purchase limit window. interval_count: type: number @@ -2268,6 +2445,35 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the customer (auto top-ups, etc.) subscriptions: type: array @@ -2293,6 +2499,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -2415,6 +2622,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." consumable: type: boolean @@ -2720,6 +2928,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -2756,6 +2965,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: type: number @@ -2789,6 +2999,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -2797,6 +3008,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: type: number @@ -2810,6 +3022,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: type: number @@ -2827,6 +3040,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -2835,6 +3049,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -2850,6 +3065,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -2873,6 +3089,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -2971,6 +3188,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -3018,6 +3236,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -3081,6 +3300,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. interval_count: type: number @@ -3107,6 +3327,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -3115,6 +3336,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: type: number @@ -3126,6 +3348,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." max_purchase: anyOf: @@ -3163,6 +3386,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -3189,6 +3413,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -3205,6 +3430,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -3224,6 +3450,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: type: boolean @@ -3238,6 +3465,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: - attach_action @@ -3414,6 +3642,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -3461,6 +3690,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -3524,6 +3754,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. interval_count: type: number @@ -3550,6 +3781,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -3558,6 +3790,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: type: number @@ -3569,6 +3802,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." max_purchase: anyOf: @@ -3606,6 +3840,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -3632,6 +3867,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -3648,6 +3884,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -3667,6 +3904,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: type: boolean @@ -3681,6 +3919,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: - attach_action @@ -3835,6 +4074,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -3882,6 +4122,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -3945,6 +4186,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. interval_count: type: number @@ -3971,6 +4213,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -3979,6 +4222,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: type: number @@ -3990,6 +4234,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." max_purchase: anyOf: @@ -4027,6 +4272,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -4053,6 +4299,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -4069,6 +4316,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -4088,6 +4336,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: type: boolean @@ -4102,6 +4351,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: - attach_action @@ -4266,6 +4516,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -4305,6 +4556,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: type: number @@ -4338,6 +4590,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -4346,6 +4599,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: type: number @@ -4359,6 +4613,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: type: number @@ -4376,6 +4631,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -4384,6 +4640,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -4399,6 +4656,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -4423,6 +4681,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -4506,6 +4765,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -4553,6 +4813,7 @@ paths: - single_use - continuous_use - credit_system + type: string description: The type of the feature display: anyOf: @@ -4616,6 +4877,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the feature balance resets (e.g. 'month', 'year'). For consumable features, usage resets to 0 and included units are restored. interval_count: type: number @@ -4642,6 +4904,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -4650,6 +4913,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval for this price. For consumable features, should match reset.interval. interval_count: type: number @@ -4661,6 +4925,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for features like seats where customers pay upfront, 'usage_based' for pay-as-you-go after included usage." max_purchase: anyOf: @@ -4698,6 +4963,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -4724,6 +4990,7 @@ paths: - day - month - year + type: string description: Unit of time for the trial duration ('day', 'month', 'year'). card_required: type: boolean @@ -4740,6 +5007,7 @@ paths: enum: - sandbox - live + type: string description: Environment this plan belongs to ('sandbox' or 'live'). archived: type: boolean @@ -4759,6 +5027,7 @@ paths: enum: - active - scheduled + type: string description: The customer's current status with this plan. 'active' if attached, 'scheduled' if pending activation. canceling: type: boolean @@ -4773,6 +5042,7 @@ paths: - downgrade - none - purchase + type: string description: The action that would occur if this plan were attached to the customer. required: - attach_action @@ -4987,6 +5257,7 @@ paths: - boolean - metered - credit_system + type: string description: The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system. consumable: type: boolean @@ -5060,6 +5331,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." consumable: type: boolean @@ -5172,6 +5444,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." consumable: type: boolean @@ -5268,6 +5541,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." consumable: type: boolean @@ -5411,6 +5685,7 @@ paths: - boolean - metered - credit_system + type: string description: The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system. consumable: type: boolean @@ -5482,6 +5757,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." consumable: type: boolean @@ -5742,6 +6018,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -5781,6 +6058,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: type: number @@ -5814,6 +6092,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -5822,6 +6101,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: type: number @@ -5835,6 +6115,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: type: number @@ -5852,6 +6133,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -5860,6 +6142,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -5875,6 +6158,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -5899,6 +6183,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -5933,12 +6218,14 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. redirect_mode: enum: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. default: if_required subscription_id: @@ -5968,6 +6255,7 @@ paths: enum: - immediate - end_of_cycle + type: string description: When the plan change should take effect. 'immediate' applies now, 'end_of_cycle' schedules for the end of the current billing cycle. By default, upgrades are immediate and downgrades are scheduled. checkout_session_params: type: object @@ -6084,6 +6372,7 @@ paths: - 3ds_required - payment_method_required - payment_failed + type: string description: The type of action required to complete the payment. reason: type: string @@ -6181,6 +6470,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -6220,6 +6510,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: type: number @@ -6253,6 +6544,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -6261,6 +6553,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: type: number @@ -6274,6 +6567,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: type: number @@ -6291,6 +6585,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -6299,6 +6594,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -6314,6 +6610,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -6368,6 +6665,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -6425,6 +6723,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. default: if_required new_billing_subscription: @@ -6461,6 +6760,35 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. required: - feature_id @@ -6532,6 +6860,7 @@ paths: - 3ds_required - payment_method_required - payment_failed + type: string description: The type of action required to complete the payment. reason: type: string @@ -6675,6 +7004,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -6714,6 +7044,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: type: number @@ -6747,6 +7078,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -6755,6 +7087,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: type: number @@ -6768,6 +7101,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: type: number @@ -6785,6 +7119,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -6793,6 +7128,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -6808,6 +7144,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -6832,6 +7169,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -6866,12 +7204,14 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. redirect_mode: enum: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. default: if_required subscription_id: @@ -6901,6 +7241,7 @@ paths: enum: - immediate - end_of_cycle + type: string description: When the plan change should take effect. 'immediate' applies now, 'end_of_cycle' schedules for the end of the current billing cycle. By default, upgrades are immediate and downgrades are scheduled. checkout_session_params: type: object @@ -7204,10 +7545,22 @@ paths: - type: number - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. - required: - - plan_id + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + required: + - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being added or updated. outgoing: type: array @@ -7240,10 +7593,22 @@ paths: - type: number - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being removed or ended. required: - customer_id @@ -7335,6 +7700,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -7374,6 +7740,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: type: number @@ -7407,6 +7774,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -7415,6 +7783,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: type: number @@ -7428,6 +7797,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: type: number @@ -7445,6 +7815,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -7453,6 +7824,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -7468,6 +7840,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -7522,6 +7895,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -7579,6 +7953,7 @@ paths: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. default: if_required new_billing_subscription: @@ -7615,6 +7990,35 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. required: - feature_id @@ -7873,10 +8277,22 @@ paths: - type: number - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being added or updated. outgoing: type: array @@ -7909,10 +8325,22 @@ paths: - type: number - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being removed or ended. required: - customer_id @@ -8003,6 +8431,8 @@ paths: @param noBillingChanges - If true, the subscription is updated internally without applying billing changes in Stripe. (optional) + @param recalculateBalances - Controls whether balances should be recalculated during the subscription update. (optional) + @returns A billing response with customer ID, invoice details, and payment URL (if next action is required). tags: @@ -8064,6 +8494,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -8103,6 +8534,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: type: number @@ -8136,6 +8568,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -8144,6 +8577,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: type: number @@ -8157,6 +8591,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: type: number @@ -8174,6 +8609,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -8182,6 +8618,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -8197,6 +8634,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -8221,6 +8659,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -8255,12 +8694,14 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. redirect_mode: enum: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. default: if_required subscription_id: @@ -8271,10 +8712,20 @@ paths: - cancel_immediately - cancel_end_of_cycle - uncancel + type: string description: Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. no_billing_changes: type: boolean description: If true, the subscription is updated internally without applying billing changes in Stripe. + recalculate_balances: + type: object + properties: + enabled: + type: boolean + description: If true, recalculates balances during the subscription update. Only applicable when updating feature quantities. + required: + - enabled + description: Controls whether balances should be recalculated during the subscription update. required: - customer_id title: UpdateSubscriptionParams @@ -8340,6 +8791,7 @@ paths: - 3ds_required - payment_method_required - payment_failed + type: string description: The type of action required to complete the payment. reason: type: string @@ -8408,6 +8860,8 @@ paths: @param noBillingChanges - If true, the subscription is updated internally without applying billing changes in Stripe. (optional) + @param recalculateBalances - Controls whether balances should be recalculated during the subscription update. (optional) + @returns A preview response with line items showing prorated charges or credits for the proposed changes. tags: @@ -8469,6 +8923,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -8508,6 +8963,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: type: number @@ -8541,6 +8997,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -8549,6 +9006,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: type: number @@ -8562,6 +9020,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: type: number @@ -8579,6 +9038,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -8587,6 +9047,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -8602,6 +9063,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -8626,6 +9088,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -8660,12 +9123,14 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. redirect_mode: enum: - always - if_required - never + type: string description: Controls when to return a checkout URL. 'always' returns a URL even if payment succeeds, 'if_required' only when payment action is needed, 'never' disables redirects. default: if_required subscription_id: @@ -8676,10 +9141,20 @@ paths: - cancel_immediately - cancel_end_of_cycle - uncancel + type: string description: Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. no_billing_changes: type: boolean description: If true, the subscription is updated internally without applying billing changes in Stripe. + recalculate_balances: + type: object + properties: + enabled: + type: boolean + description: If true, recalculates balances during the subscription update. Only applicable when updating feature quantities. + required: + - enabled + description: Controls whether balances should be recalculated during the subscription update. required: - customer_id title: PreviewUpdateParams @@ -8932,10 +9407,22 @@ paths: - type: number - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being added or updated. outgoing: type: array @@ -8968,10 +9455,22 @@ paths: - type: number - type: "null" description: When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. + canceled_at: + anyOf: + - type: number + - type: "null" + description: When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + expires_at: + anyOf: + - type: number + - type: "null" + description: When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. required: - plan_id - feature_quantities - effective_at + - canceled_at + - expires_at description: Products or subscription changes being removed or ended. intent: enum: @@ -8981,6 +9480,7 @@ paths: - cancel_end_of_cycle - uncancel - none + type: string required: - customer_id - line_items @@ -9118,6 +9618,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval (e.g. 'month', 'year'). interval_count: type: number @@ -9157,6 +9658,7 @@ paths: - quarter - semi_annual - year + type: string description: Interval at which balance resets (e.g. 'month', 'year'). For consumable features only. interval_count: type: number @@ -9190,6 +9692,7 @@ paths: enum: - graduated - volume + type: string interval: enum: - one_off @@ -9198,6 +9701,7 @@ paths: - quarter - semi_annual - year + type: string description: Billing interval. For consumable features, should match reset.interval. interval_count: type: number @@ -9211,6 +9715,7 @@ paths: enum: - prepaid - usage_based + type: string description: "'prepaid' for upfront payment (seats), 'usage_based' for pay-as-you-go." max_purchase: type: number @@ -9228,6 +9733,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string description: Billing behavior when quantity increases mid-cycle. on_decrease: enum: @@ -9236,6 +9742,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string description: Credit behavior when quantity decreases mid-cycle. required: - on_increase @@ -9251,6 +9758,7 @@ paths: enum: - month - forever + type: string description: When rolled over units expire. expiry_duration_length: type: number @@ -9275,6 +9783,7 @@ paths: - day - month - year + type: string default: month description: Unit of time for the trial ('day', 'month', 'year'). card_required: @@ -9292,6 +9801,7 @@ paths: enum: - prorate_immediately - none + type: string description: How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges. subscription_id: type: string @@ -9440,6 +9950,7 @@ paths: - quarter - semi_annual - year + type: string description: The interval at which the balance resets (e.g., 'month', 'day', 'year'). interval_count: type: number @@ -9520,6 +10031,7 @@ paths: - quarter - semi_annual - year + type: string description: Target a specific balance by its reset interval. Use when the customer has multiple balances for the same feature with different reset intervals. included_grant: type: number @@ -9589,6 +10101,7 @@ paths: - quarter - semi_annual - year + type: string description: Target a specific balance by its reset interval. Use when the customer has multiple balances for the same feature with different reset intervals. required: - customer_id @@ -9631,6 +10144,7 @@ paths: enum: - confirm - release + type: string description: Use 'confirm' to commit the deduction, or 'release' to return the held balance. override_value: type: number @@ -9845,6 +10359,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." consumable: type: boolean @@ -9912,6 +10427,7 @@ paths: enum: - usage_limit - feature_flag + type: string description: The reason access was denied. 'usage_limit' means the customer exceeded their balance, 'feature_flag' means the feature is not included in their plan. title: type: string @@ -9945,6 +10461,7 @@ paths: enum: - sandbox - live + type: string description: The environment of the product is_add_on: type: boolean @@ -9972,6 +10489,7 @@ paths: - feature - priced_feature - price + type: string - type: "null" description: The type of the product item feature_id: @@ -9986,6 +10504,7 @@ paths: - continuous_use - boolean - static + type: string - type: "null" description: Single use features are used once and then depleted, like API calls or credits. Continuous use features are those being used on an ongoing-basis, like storage or seats. included_usage: @@ -10006,6 +10525,7 @@ paths: - quarter - semi_annual - year + type: string - type: "null" description: The reset or billing interval of the product item. If null, feature will have no reset date, and if there's a price, it will be billed one-off. interval_count: @@ -10032,6 +10552,7 @@ paths: - enum: - graduated - volume + type: string - type: "null" description: "How tiers are applied: graduated (split across bands) or volume (flat rate for the matched tier). Defaults to graduated." usage_model: @@ -10039,6 +10560,7 @@ paths: - enum: - prepaid - pay_per_use + type: string - type: "null" description: Whether the feature should be prepaid upfront or billed for how much they use end of billing period. billing_units: @@ -10096,6 +10618,7 @@ paths: enum: - month - forever + type: string default: month length: type: number @@ -10110,6 +10633,7 @@ paths: - prorate_immediately - prorate_next_cycle - bill_next_cycle + type: string - type: "null" on_decrease: anyOf: @@ -10119,6 +10643,7 @@ paths: - prorate_next_cycle - none - no_prorations + type: string - type: "null" - type: "null" description: Configuration for rollover and proration behavior of the feature. @@ -10133,6 +10658,7 @@ paths: - day - month - year + type: string description: The duration type of the free trial length: type: number @@ -10172,6 +10698,7 @@ paths: - cancel - expired - past_due + type: string description: Scenario for when this product is used in attach flows properties: type: object @@ -10598,12 +11125,14 @@ paths: - last_cycle - 1bc - 3bc + type: string description: Time range to aggregate events for. Either range or custom_range must be provided bin_size: enum: - day - hour - month + type: string default: day description: Size of the time bins to aggregate events for. Defaults to hour if range is 24h, otherwise day custom_range: @@ -10818,6 +11347,35 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. customer_data: $ref: "#/components/schemas/CustomerData" @@ -10873,6 +11431,7 @@ paths: enum: - sandbox - live + type: string description: The environment (sandbox/live) subscriptions: type: array @@ -10898,6 +11457,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -11017,6 +11577,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." consumable: type: boolean @@ -11095,6 +11656,35 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. invoices: type: array @@ -11286,6 +11876,7 @@ paths: enum: - sandbox - live + type: string description: The environment (sandbox/live) subscriptions: type: array @@ -11311,6 +11902,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -11430,6 +12022,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." consumable: type: boolean @@ -11508,6 +12101,35 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. invoices: type: array @@ -11658,6 +12280,35 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls to replace on the entity. required: - entity_id @@ -11705,6 +12356,7 @@ paths: enum: - sandbox - live + type: string description: The environment (sandbox/live) subscriptions: type: array @@ -11730,6 +12382,7 @@ paths: enum: - active - scheduled + type: string description: Current status of the subscription. past_due: type: boolean @@ -11849,6 +12502,7 @@ paths: - boolean - metered - credit_system + type: string description: "Feature type: 'boolean' for on/off access, 'metered' for usage-tracked features, 'credit_system' for unified credit pools." consumable: type: boolean @@ -11927,6 +12581,35 @@ paths: minimum: 0 description: Maximum allowed overage spend for the target feature. description: List of overage spend limits per feature. + usage_alerts: + type: array + items: + type: object + properties: + feature_id: + type: string + description: The feature ID this alert applies to. If omitted, the alert applies globally. + enabled: + type: boolean + default: true + description: Whether this usage alert is enabled. + threshold: + type: number + minimum: 0 + description: The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + threshold_type: + enum: + - usage + - usage_percentage + type: string + description: Whether the threshold is an absolute usage count or a percentage of the usage allowance. + name: + type: string + description: Optional user-defined label to distinguish multiple alerts on the same feature. + required: + - threshold + - threshold_type + description: List of usage alert configurations per feature. description: Billing controls for the entity. invoices: type: array @@ -12207,3 +12890,310 @@ x-speakeasy-globals: type: string default: 2.2.0 x-speakeasy-globals-hidden: true +webhooks: + balances.usage_alert_triggered: + post: + operationId: balancesUsageAlertTriggered + summary: Usage Alert Triggered + description: Fired when a customer crosses a configured usage alert threshold. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: balances.usage_alert_triggered + description: The webhook event type. + data: + examples: + - customer_id: org_123 + feature_id: api_calls + entity_id: workspace_abc + usage_alert: + name: 80% usage warning + threshold: 80 + threshold_type: usage_percentage_threshold + type: object + properties: + customer_id: + description: The ID of the customer whose usage alert was triggered. + type: string + feature_id: + description: The feature ID the alert applies to. + type: string + entity_id: + description: The entity ID the alert applies to, if the usage was entity-scoped. + type: string + usage_alert: + description: Details of the usage alert that was triggered. + type: object + properties: + name: + description: User-defined label for the alert, if provided. + type: string + threshold: + description: The threshold value that was crossed. + type: number + threshold_type: + description: Whether the threshold is an absolute usage count or a percentage. + type: string + enum: + - usage + - usage_percentage + required: + - threshold + - threshold_type + additionalProperties: false + required: + - customer_id + - feature_id + - usage_alert + additionalProperties: false + example: + type: balances.usage_alert_triggered + data: + customer_id: org_123 + feature_id: api_calls + entity_id: workspace_abc + usage_alert: + name: 80% usage warning + threshold: 80 + threshold_type: usage_percentage_threshold + responses: + "200": + description: Webhook received successfully. + balances.limit_reached: + post: + operationId: balancesLimitReached + summary: Limit Reached + description: Fired when a customer reaches the limit for a feature (included allowance, max purchase, or spend limit). + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: balances.limit_reached + description: The webhook event type. + data: + examples: + - customer_id: org_123 + entity_id: workspace_abc + feature_id: api_calls + limit_type: included + type: object + properties: + customer_id: + description: The ID of the customer who hit the limit. + type: string + entity_id: + description: The entity ID, if the limit was reached on a specific entity. + type: string + feature_id: + description: The feature ID whose limit was reached. + type: string + limit_type: + description: "Which limit was hit: included allowance, max purchase cap, or spend limit." + type: string + enum: + - included + - max_purchase + - spend_limit + required: + - customer_id + - feature_id + - limit_type + additionalProperties: false + example: + type: balances.limit_reached + data: + customer_id: org_123 + entity_id: workspace_abc + feature_id: api_calls + limit_type: included + responses: + "200": + description: Webhook received successfully. + vercel.resources.deleted: + post: + operationId: vercelResourcesDeleted + summary: Resource Deleted + description: When a Vercel resource is deleted, you'll need to handle de-provisioning any API keys or other non-Autumn controlled data for this user. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.resources.deleted + description: The webhook event type. + data: + type: object + properties: + resource: + description: The resource that was deleted. + type: object + properties: + id: + description: The unique identifier of the deleted resource. + type: string + required: + - id + additionalProperties: false + installation_id: + description: The Vercel integration configuration ID. + type: string + required: + - resource + - installation_id + additionalProperties: false + responses: + "200": + description: Webhook received successfully. + vercel.resources.provisioned: + post: + operationId: vercelResourcesProvisioned + summary: Resource Provisioned + description: When a Vercel resource is created, you'll need to provision a secret key for your service. Then you can use the provided access token to patch the resource's secrets. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.resources.provisioned + description: The webhook event type. + data: + type: object + properties: + resource: + description: The resource that was provisioned. + type: object + properties: + id: + description: The unique identifier of the provisioned resource. + type: string + name: + description: The display name of the provisioned resource. + type: string + required: + - id + - name + additionalProperties: false + installation_id: + description: The Vercel integration configuration ID. + type: string + access_token: + description: An access token that can be used to patch the resource's secrets. + type: string + required: + - resource + - installation_id + - access_token + additionalProperties: false + responses: + "200": + description: Webhook received successfully. + vercel.resources.rotate_secrets: + post: + operationId: vercelResourcesRotateSecrets + summary: Rotate Secrets + description: This event is sent when Vercel requires a resource's secrets to be rotated. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.resources.rotate_secrets + description: The webhook event type. + data: + type: object + properties: + resource: + description: The resource whose secrets should be rotated. + type: object + properties: + id: + description: The unique identifier of the resource. + type: string + required: + - id + additionalProperties: false + installation_id: + description: The Vercel integration configuration ID. + type: string + vercel_request_body: + description: The raw request body from Vercel's rotation request. + required: + - resource + - installation_id + - vercel_request_body + additionalProperties: false + responses: + "200": + description: Webhook received successfully. + vercel.webhooks.event: + post: + operationId: vercelWebhooksEvent + summary: Webhook Event + description: Passthrough webhook for Vercel events. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - type + - data + properties: + type: + type: string + const: vercel.webhooks.event + description: The webhook event type. + data: + type: object + properties: + installation_id: + description: The Vercel integration configuration ID. + type: string + event: + description: The raw Vercel webhook event payload. + required: + - installation_id + - event + additionalProperties: false + responses: + "200": + description: Webhook received successfully. diff --git a/packages/sdk/.speakeasy/workflow.lock b/packages/sdk/.speakeasy/workflow.lock index 40c79d6e3..d19d9afe2 100644 --- a/packages/sdk/.speakeasy/workflow.lock +++ b/packages/sdk/.speakeasy/workflow.lock @@ -1,16 +1,16 @@ -speakeasyVersion: 1.757.1 +speakeasyVersion: 1.719.0 sources: Autumn API: sourceNamespace: autumn-api - sourceRevisionDigest: sha256:5eb5071d5420195da5f83032ba1123effe8934181288e8b08695a1d0fa5d8734 - sourceBlobDigest: sha256:163c4ad9005ca92206e93dc4233f6e3932b1927e9e3a797638dfeae905d4450d + sourceRevisionDigest: sha256:2bcc989537f4586adcc8402828479bcd9c4b42ac9345534d31f2c3a345fa00ed + sourceBlobDigest: sha256:2260cf8c08d26c02fffa4368afe9735bb6f202e52f95105bd3705f0953ae6f78 tags: - latest - - 2.1.0 + - 2.2.0 Autumn API Stripped: sourceNamespace: autumn-api-stripped - sourceRevisionDigest: sha256:4d9487e22543b3e51d08789572c3d90ac3d02564991610bc11541e34b20bc60b - sourceBlobDigest: sha256:26275a1f91ac6dd56e87f72778c860fce556d2a433dee3a8e634b7dfecf842d8 + sourceRevisionDigest: sha256:73b97e1e3131f4a268daddbc9107f4e77beaf628f9308078516cb84848a94480 + sourceBlobDigest: sha256:125b8a9f6111ceb84655b9f7eae781f5a566c8946bee87632a501e382f0a06db tags: - latest - 2.2.0 @@ -18,17 +18,17 @@ targets: autumn: source: Autumn API sourceNamespace: autumn-api - sourceRevisionDigest: sha256:5eb5071d5420195da5f83032ba1123effe8934181288e8b08695a1d0fa5d8734 - sourceBlobDigest: sha256:163c4ad9005ca92206e93dc4233f6e3932b1927e9e3a797638dfeae905d4450d + sourceRevisionDigest: sha256:2bcc989537f4586adcc8402828479bcd9c4b42ac9345534d31f2c3a345fa00ed + sourceBlobDigest: sha256:2260cf8c08d26c02fffa4368afe9735bb6f202e52f95105bd3705f0953ae6f78 codeSamplesNamespace: autumn-api-typescript-code-samples - codeSamplesRevisionDigest: sha256:24d7a293e42a7de5b564b2a1cb011cbbaa81592fe71b7906c64090b1bdb33d4e + codeSamplesRevisionDigest: sha256:ed725e6d550745811ba55b8f9abcfd47782d47cfe73d09757a4bbf56b240b6a4 autumn-python: source: Autumn API Stripped sourceNamespace: autumn-api-stripped - sourceRevisionDigest: sha256:4d9487e22543b3e51d08789572c3d90ac3d02564991610bc11541e34b20bc60b - sourceBlobDigest: sha256:26275a1f91ac6dd56e87f72778c860fce556d2a433dee3a8e634b7dfecf842d8 + sourceRevisionDigest: sha256:73b97e1e3131f4a268daddbc9107f4e77beaf628f9308078516cb84848a94480 + sourceBlobDigest: sha256:125b8a9f6111ceb84655b9f7eae781f5a566c8946bee87632a501e382f0a06db codeSamplesNamespace: autumn-api-python-code-samples - codeSamplesRevisionDigest: sha256:6f5e1b71a197e371ec518024628dbcf0f5f6189bdbd3ffae55f80c7efb43bff8 + codeSamplesRevisionDigest: sha256:9804d178e8ccbe6f452890582dfdd15bdcd88c54ad12ffaec9e7b887b5792082 workflow: workflowVersion: 1.0.0 speakeasyVersion: pinned diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 683ea3b43..016bcc73d 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -380,6 +380,7 @@ const response = await client.billing.update({ customerId: "cus_123", planId: "p @param subscriptionId - A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. (optional) @param cancelAction - Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. (optional) @param noBillingChanges - If true, the subscription is updated internally without applying billing changes in Stripe. (optional) +@param recalculateBalances - Controls whether balances should be recalculated during the subscription update. (optional) @returns A billing response with customer ID, invoice details, and payment URL (if next action is required). * [previewUpdate](docs/sdks/billing/README.md#previewupdate) - Previews the billing changes that would occur when updating a subscription, without actually making any changes. @@ -404,6 +405,7 @@ const response = await client.billing.previewUpdate({ customerId: "cus_123", pla @param subscriptionId - A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. (optional) @param cancelAction - Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. (optional) @param noBillingChanges - If true, the subscription is updated internally without applying billing changes in Stripe. (optional) +@param recalculateBalances - Controls whether balances should be recalculated during the subscription update. (optional) @returns A preview response with line items showing prorated charges or credits for the proposed changes. * [openCustomerPortal](docs/sdks/billing/README.md#opencustomerportal) - Create a billing portal session for a customer to manage their subscription. @@ -797,6 +799,7 @@ const response = await client.billing.previewUpdate({ customerId: "cus_123", pla @param subscriptionId - A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. (optional) @param cancelAction - Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. (optional) @param noBillingChanges - If true, the subscription is updated internally without applying billing changes in Stripe. (optional) +@param recalculateBalances - Controls whether balances should be recalculated during the subscription update. (optional) @returns A preview response with line items showing prorated charges or credits for the proposed changes. - [`billingSetupPayment`](docs/sdks/billing/README.md#setuppayment) - Create a payment setup session for a customer to add or update their payment method. @@ -834,6 +837,7 @@ const response = await client.billing.update({ customerId: "cus_123", planId: "p @param subscriptionId - A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. (optional) @param cancelAction - Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. (optional) @param noBillingChanges - If true, the subscription is updated internally without applying billing changes in Stripe. (optional) +@param recalculateBalances - Controls whether balances should be recalculated during the subscription update. (optional) @returns A billing response with customer ID, invoice details, and payment URL (if next action is required). - [`check`](docs/sdks/autumn/README.md#check) - Checks whether a customer currently has enough balance to use a feature. diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 84d7ead7a..fe58d7845 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -30,7 +30,7 @@ "@eslint/js": "^9.26.0", "eslint": "^9.26.0", "globals": "^15.14.0", - "tshy": "^3.3.2", + "tshy": "^2.0.0", "typescript": "~5.8.3", "typescript-eslint": "^8.26.0" }, diff --git a/packages/sdk/src/funcs/billing-preview-update.ts b/packages/sdk/src/funcs/billing-preview-update.ts index bcbdaddd7..69d04d5f4 100644 --- a/packages/sdk/src/funcs/billing-preview-update.ts +++ b/packages/sdk/src/funcs/billing-preview-update.ts @@ -48,6 +48,7 @@ import { Result } from "../types/fp.js"; * @param subscriptionId - A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. (optional) * @param cancelAction - Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. (optional) * @param noBillingChanges - If true, the subscription is updated internally without applying billing changes in Stripe. (optional) + * @param recalculateBalances - Controls whether balances should be recalculated during the subscription update. (optional) * * @returns A preview response with line items showing prorated charges or credits for the proposed changes. */ diff --git a/packages/sdk/src/funcs/billing-update.ts b/packages/sdk/src/funcs/billing-update.ts index c7c08f719..43bceea02 100644 --- a/packages/sdk/src/funcs/billing-update.ts +++ b/packages/sdk/src/funcs/billing-update.ts @@ -60,6 +60,7 @@ import { Result } from "../types/fp.js"; * @param subscriptionId - A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. (optional) * @param cancelAction - Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. (optional) * @param noBillingChanges - If true, the subscription is updated internally without applying billing changes in Stripe. (optional) + * @param recalculateBalances - Controls whether balances should be recalculated during the subscription update. (optional) * * @returns A billing response with customer ID, invoice details, and payment URL (if next action is required). */ diff --git a/packages/sdk/src/lib/config.ts b/packages/sdk/src/lib/config.ts index ef0ca2061..468ec0db0 100644 --- a/packages/sdk/src/lib/config.ts +++ b/packages/sdk/src/lib/config.ts @@ -67,6 +67,6 @@ export const SDK_METADATA = { language: "typescript", openapiDocVersion: "2.2.0", sdkVersion: "0.10.17", - genVersion: "2.866.2", - userAgent: "speakeasy-sdk/typescript 0.10.17 2.866.2 2.2.0 @useautumn/sdk", + genVersion: "2.824.1", + userAgent: "speakeasy-sdk/typescript 0.10.17 2.824.1 2.2.0 @useautumn/sdk", } as const; diff --git a/packages/sdk/src/lib/files.ts b/packages/sdk/src/lib/files.ts index 6ca6b37d3..0344cd046 100644 --- a/packages/sdk/src/lib/files.ts +++ b/packages/sdk/src/lib/files.ts @@ -80,25 +80,3 @@ export function getContentTypeFromFileName(fileName: string): string | null { return mimeTypes[ext] || null; } - -/** - * Creates a Blob from file content with the given MIME type. - * - * Node.js Buffers are Uint8Array subclasses that may share a pooled - * ArrayBuffer (byteOffset > 0, byteLength < buffer.byteLength). Passing - * such a Buffer directly to `new Blob([buf])` can include the entire - * underlying pool on some runtimes, producing a Blob with extra bytes - * that corrupts multipart uploads. - * - * Copying into a standalone Uint8Array ensures the Blob receives only the - * intended bytes regardless of runtime behaviour. - */ -export function bytesToBlob( - content: Uint8Array | ArrayBuffer | Blob | string, - contentType: string, -): Blob { - if (content instanceof Uint8Array) { - return new Blob([new Uint8Array(content)], { type: contentType }); - } - return new Blob([content as BlobPart], { type: contentType }); -} diff --git a/packages/sdk/src/lib/matchers.ts b/packages/sdk/src/lib/matchers.ts index 9a72b3fe2..2f22acee9 100644 --- a/packages/sdk/src/lib/matchers.ts +++ b/packages/sdk/src/lib/matchers.ts @@ -251,9 +251,8 @@ export function match( raw = body; break; default: - throw new Error( - `Unsupported response type: ${encoding satisfies never}`, - ); + encoding satisfies never; + throw new Error(`Unsupported response type: ${encoding}`); } if (matcher.enc === "fail") { diff --git a/packages/sdk/src/lib/security.ts b/packages/sdk/src/lib/security.ts index ee8dd5861..659b39acb 100644 --- a/packages/sdk/src/lib/security.ts +++ b/packages/sdk/src/lib/security.ts @@ -198,7 +198,8 @@ export function resolveSecurity( applyBearer(state, spec); break; default: - throw SecurityError.unrecognizedType((spec satisfies never, type)); + spec satisfies never; + throw SecurityError.unrecognizedType(type); } }); diff --git a/packages/sdk/src/models/billing-update-op.ts b/packages/sdk/src/models/billing-update-op.ts index 055c273ee..32ffe830b 100644 --- a/packages/sdk/src/models/billing-update-op.ts +++ b/packages/sdk/src/models/billing-update-op.ts @@ -414,6 +414,16 @@ export type BillingUpdateCancelAction = ClosedEnum< typeof BillingUpdateCancelAction >; +/** + * Controls whether balances should be recalculated during the subscription update. + */ +export type BillingUpdateRecalculateBalances = { + /** + * If true, recalculates balances during the subscription update. Only applicable when updating feature quantities. + */ + enabled: boolean; +}; + export type UpdateSubscriptionParams = { /** * The ID of the customer to attach the plan to. @@ -463,6 +473,10 @@ export type UpdateSubscriptionParams = { * If true, the subscription is updated internally without applying billing changes in Stripe. */ noBillingChanges?: boolean | undefined; + /** + * Controls whether balances should be recalculated during the subscription update. + */ + recalculateBalances?: BillingUpdateRecalculateBalances | undefined; }; /** @@ -1003,6 +1017,29 @@ export const BillingUpdateCancelAction$outboundSchema: z.ZodMiniEnum< typeof BillingUpdateCancelAction > = z.enum(BillingUpdateCancelAction); +/** @internal */ +export type BillingUpdateRecalculateBalances$Outbound = { + enabled: boolean; +}; + +/** @internal */ +export const BillingUpdateRecalculateBalances$outboundSchema: z.ZodMiniType< + BillingUpdateRecalculateBalances$Outbound, + BillingUpdateRecalculateBalances +> = z.object({ + enabled: z.boolean(), +}); + +export function billingUpdateRecalculateBalancesToJSON( + billingUpdateRecalculateBalances: BillingUpdateRecalculateBalances, +): string { + return JSON.stringify( + BillingUpdateRecalculateBalances$outboundSchema.parse( + billingUpdateRecalculateBalances, + ), + ); +} + /** @internal */ export type UpdateSubscriptionParams$Outbound = { customer_id: string; @@ -1017,6 +1054,7 @@ export type UpdateSubscriptionParams$Outbound = { subscription_id?: string | undefined; cancel_action?: string | undefined; no_billing_changes?: boolean | undefined; + recalculate_balances?: BillingUpdateRecalculateBalances$Outbound | undefined; }; /** @internal */ @@ -1046,6 +1084,9 @@ export const UpdateSubscriptionParams$outboundSchema: z.ZodMiniType< subscriptionId: z.optional(z.string()), cancelAction: z.optional(BillingUpdateCancelAction$outboundSchema), noBillingChanges: z.optional(z.boolean()), + recalculateBalances: z.optional( + z.lazy(() => BillingUpdateRecalculateBalances$outboundSchema), + ), }), z.transform((v) => { return remap$(v, { @@ -1059,6 +1100,7 @@ export const UpdateSubscriptionParams$outboundSchema: z.ZodMiniType< subscriptionId: "subscription_id", cancelAction: "cancel_action", noBillingChanges: "no_billing_changes", + recalculateBalances: "recalculate_balances", }); }), ); diff --git a/packages/sdk/src/models/create-entity-op.ts b/packages/sdk/src/models/create-entity-op.ts index 3e065c21a..44f5f23e6 100644 --- a/packages/sdk/src/models/create-entity-op.ts +++ b/packages/sdk/src/models/create-entity-op.ts @@ -6,7 +6,7 @@ import * as z from "zod/v4-mini"; import { remap as remap$ } from "../lib/primitives.js"; import { safeParse } from "../lib/schemas.js"; import * as openEnums from "../types/enums.js"; -import { OpenEnum } from "../types/enums.js"; +import { ClosedEnum, OpenEnum } from "../types/enums.js"; import { Result as SafeParseResult } from "../types/fp.js"; import * as types from "../types/primitives.js"; import { Balance, Balance$inboundSchema } from "./balance.js"; @@ -37,6 +37,43 @@ export type CreateEntitySpendLimitRequest = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const CreateEntityThresholdTypeRequestBody = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type CreateEntityThresholdTypeRequestBody = ClosedEnum< + typeof CreateEntityThresholdTypeRequestBody +>; + +export type CreateEntityUsageAlertRequestBody = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled?: boolean | undefined; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: CreateEntityThresholdTypeRequestBody; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls for the entity. */ @@ -45,6 +82,10 @@ export type CreateEntityBillingControlsRequest = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; export type CreateEntityParams = { @@ -288,6 +329,43 @@ export type CreateEntitySpendLimitResponse = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const CreateEntityThresholdTypeResponse = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type CreateEntityThresholdTypeResponse = OpenEnum< + typeof CreateEntityThresholdTypeResponse +>; + +export type CreateEntityUsageAlertResponse = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled: boolean; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: CreateEntityThresholdTypeResponse; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls for the entity. */ @@ -296,6 +374,10 @@ export type CreateEntityBillingControlsResponse = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; export type CreateEntityInvoice = { @@ -406,9 +488,54 @@ export function createEntitySpendLimitRequestToJSON( ); } +/** @internal */ +export const CreateEntityThresholdTypeRequestBody$outboundSchema: z.ZodMiniEnum< + typeof CreateEntityThresholdTypeRequestBody +> = z.enum(CreateEntityThresholdTypeRequestBody); + +/** @internal */ +export type CreateEntityUsageAlertRequestBody$Outbound = { + feature_id?: string | undefined; + enabled: boolean; + threshold: number; + threshold_type: string; + name?: string | undefined; +}; + +/** @internal */ +export const CreateEntityUsageAlertRequestBody$outboundSchema: z.ZodMiniType< + CreateEntityUsageAlertRequestBody$Outbound, + CreateEntityUsageAlertRequestBody +> = z.pipe( + z.object({ + featureId: z.optional(z.string()), + enabled: z._default(z.boolean(), true), + threshold: z.number(), + thresholdType: CreateEntityThresholdTypeRequestBody$outboundSchema, + name: z.optional(z.string()), + }), + z.transform((v) => { + return remap$(v, { + featureId: "feature_id", + thresholdType: "threshold_type", + }); + }), +); + +export function createEntityUsageAlertRequestBodyToJSON( + createEntityUsageAlertRequestBody: CreateEntityUsageAlertRequestBody, +): string { + return JSON.stringify( + CreateEntityUsageAlertRequestBody$outboundSchema.parse( + createEntityUsageAlertRequestBody, + ), + ); +} + /** @internal */ export type CreateEntityBillingControlsRequest$Outbound = { spend_limits?: Array | undefined; + usage_alerts?: Array | undefined; }; /** @internal */ @@ -420,10 +547,14 @@ export const CreateEntityBillingControlsRequest$outboundSchema: z.ZodMiniType< spendLimits: z.optional( z.array(z.lazy(() => CreateEntitySpendLimitRequest$outboundSchema)), ), + usageAlerts: z.optional( + z.array(z.lazy(() => CreateEntityUsageAlertRequestBody$outboundSchema)), + ), }), z.transform((v) => { return remap$(v, { spendLimits: "spend_limits", + usageAlerts: "usage_alerts", }); }), ); @@ -718,6 +849,42 @@ export function createEntitySpendLimitResponseFromJSON( ); } +/** @internal */ +export const CreateEntityThresholdTypeResponse$inboundSchema: z.ZodMiniType< + CreateEntityThresholdTypeResponse, + unknown +> = openEnums.inboundSchema(CreateEntityThresholdTypeResponse); + +/** @internal */ +export const CreateEntityUsageAlertResponse$inboundSchema: z.ZodMiniType< + CreateEntityUsageAlertResponse, + unknown +> = z.pipe( + z.object({ + feature_id: types.optional(types.string()), + enabled: z._default(types.boolean(), true), + threshold: types.number(), + threshold_type: CreateEntityThresholdTypeResponse$inboundSchema, + name: types.optional(types.string()), + }), + z.transform((v) => { + return remap$(v, { + "feature_id": "featureId", + "threshold_type": "thresholdType", + }); + }), +); + +export function createEntityUsageAlertResponseFromJSON( + jsonString: string, +): SafeParseResult { + return safeParse( + jsonString, + (x) => CreateEntityUsageAlertResponse$inboundSchema.parse(JSON.parse(x)), + `Failed to parse 'CreateEntityUsageAlertResponse' from JSON`, + ); +} + /** @internal */ export const CreateEntityBillingControlsResponse$inboundSchema: z.ZodMiniType< CreateEntityBillingControlsResponse, @@ -727,10 +894,14 @@ export const CreateEntityBillingControlsResponse$inboundSchema: z.ZodMiniType< spend_limits: types.optional( z.array(z.lazy(() => CreateEntitySpendLimitResponse$inboundSchema)), ), + usage_alerts: types.optional( + z.array(z.lazy(() => CreateEntityUsageAlertResponse$inboundSchema)), + ), }), z.transform((v) => { return remap$(v, { "spend_limits": "spendLimits", + "usage_alerts": "usageAlerts", }); }), ); diff --git a/packages/sdk/src/models/customer-data.ts b/packages/sdk/src/models/customer-data.ts index d519831f2..06b640118 100644 --- a/packages/sdk/src/models/customer-data.ts +++ b/packages/sdk/src/models/customer-data.ts @@ -76,6 +76,43 @@ export type CustomerDataSpendLimit = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const CustomerDataThresholdType = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type CustomerDataThresholdType = ClosedEnum< + typeof CustomerDataThresholdType +>; + +export type CustomerDataUsageAlert = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled?: boolean | undefined; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: CustomerDataThresholdType; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls for the customer (auto top-ups, etc.) */ @@ -88,6 +125,10 @@ export type CustomerDataBillingControls = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; /** @@ -241,10 +282,53 @@ export function customerDataSpendLimitToJSON( ); } +/** @internal */ +export const CustomerDataThresholdType$outboundSchema: z.ZodMiniEnum< + typeof CustomerDataThresholdType +> = z.enum(CustomerDataThresholdType); + +/** @internal */ +export type CustomerDataUsageAlert$Outbound = { + feature_id?: string | undefined; + enabled: boolean; + threshold: number; + threshold_type: string; + name?: string | undefined; +}; + +/** @internal */ +export const CustomerDataUsageAlert$outboundSchema: z.ZodMiniType< + CustomerDataUsageAlert$Outbound, + CustomerDataUsageAlert +> = z.pipe( + z.object({ + featureId: z.optional(z.string()), + enabled: z._default(z.boolean(), true), + threshold: z.number(), + thresholdType: CustomerDataThresholdType$outboundSchema, + name: z.optional(z.string()), + }), + z.transform((v) => { + return remap$(v, { + featureId: "feature_id", + thresholdType: "threshold_type", + }); + }), +); + +export function customerDataUsageAlertToJSON( + customerDataUsageAlert: CustomerDataUsageAlert, +): string { + return JSON.stringify( + CustomerDataUsageAlert$outboundSchema.parse(customerDataUsageAlert), + ); +} + /** @internal */ export type CustomerDataBillingControls$Outbound = { auto_topups?: Array | undefined; spend_limits?: Array | undefined; + usage_alerts?: Array | undefined; }; /** @internal */ @@ -259,11 +343,15 @@ export const CustomerDataBillingControls$outboundSchema: z.ZodMiniType< spendLimits: z.optional( z.array(z.lazy(() => CustomerDataSpendLimit$outboundSchema)), ), + usageAlerts: z.optional( + z.array(z.lazy(() => CustomerDataUsageAlert$outboundSchema)), + ), }), z.transform((v) => { return remap$(v, { autoTopups: "auto_topups", spendLimits: "spend_limits", + usageAlerts: "usage_alerts", }); }), ); diff --git a/packages/sdk/src/models/customer.ts b/packages/sdk/src/models/customer.ts index 4d0e40723..0196844d6 100644 --- a/packages/sdk/src/models/customer.ts +++ b/packages/sdk/src/models/customer.ts @@ -95,6 +95,41 @@ export type CustomerSpendLimit = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const CustomerThresholdType = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type CustomerThresholdType = OpenEnum; + +export type CustomerUsageAlert = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled: boolean; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: CustomerThresholdType; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls for the customer (auto top-ups, etc.) */ @@ -107,6 +142,10 @@ export type CustomerBillingControls = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; /** @@ -646,6 +685,42 @@ export function customerSpendLimitFromJSON( ); } +/** @internal */ +export const CustomerThresholdType$inboundSchema: z.ZodMiniType< + CustomerThresholdType, + unknown +> = openEnums.inboundSchema(CustomerThresholdType); + +/** @internal */ +export const CustomerUsageAlert$inboundSchema: z.ZodMiniType< + CustomerUsageAlert, + unknown +> = z.pipe( + z.object({ + feature_id: types.optional(types.string()), + enabled: z._default(types.boolean(), true), + threshold: types.number(), + threshold_type: CustomerThresholdType$inboundSchema, + name: types.optional(types.string()), + }), + z.transform((v) => { + return remap$(v, { + "feature_id": "featureId", + "threshold_type": "thresholdType", + }); + }), +); + +export function customerUsageAlertFromJSON( + jsonString: string, +): SafeParseResult { + return safeParse( + jsonString, + (x) => CustomerUsageAlert$inboundSchema.parse(JSON.parse(x)), + `Failed to parse 'CustomerUsageAlert' from JSON`, + ); +} + /** @internal */ export const CustomerBillingControls$inboundSchema: z.ZodMiniType< CustomerBillingControls, @@ -658,11 +733,15 @@ export const CustomerBillingControls$inboundSchema: z.ZodMiniType< spend_limits: types.optional( z.array(z.lazy(() => CustomerSpendLimit$inboundSchema)), ), + usage_alerts: types.optional( + z.array(z.lazy(() => CustomerUsageAlert$inboundSchema)), + ), }), z.transform((v) => { return remap$(v, { "auto_topups": "autoTopups", "spend_limits": "spendLimits", + "usage_alerts": "usageAlerts", }); }), ); diff --git a/packages/sdk/src/models/get-entity-op.ts b/packages/sdk/src/models/get-entity-op.ts index 5eba1e01d..ce36a7c97 100644 --- a/packages/sdk/src/models/get-entity-op.ts +++ b/packages/sdk/src/models/get-entity-op.ts @@ -242,6 +242,41 @@ export type GetEntitySpendLimit = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const GetEntityThresholdType = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type GetEntityThresholdType = OpenEnum; + +export type GetEntityUsageAlert = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled: boolean; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: GetEntityThresholdType; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls for the entity. */ @@ -250,6 +285,10 @@ export type GetEntityBillingControls = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; export type GetEntityInvoice = { @@ -588,6 +627,42 @@ export function getEntitySpendLimitFromJSON( ); } +/** @internal */ +export const GetEntityThresholdType$inboundSchema: z.ZodMiniType< + GetEntityThresholdType, + unknown +> = openEnums.inboundSchema(GetEntityThresholdType); + +/** @internal */ +export const GetEntityUsageAlert$inboundSchema: z.ZodMiniType< + GetEntityUsageAlert, + unknown +> = z.pipe( + z.object({ + feature_id: types.optional(types.string()), + enabled: z._default(types.boolean(), true), + threshold: types.number(), + threshold_type: GetEntityThresholdType$inboundSchema, + name: types.optional(types.string()), + }), + z.transform((v) => { + return remap$(v, { + "feature_id": "featureId", + "threshold_type": "thresholdType", + }); + }), +); + +export function getEntityUsageAlertFromJSON( + jsonString: string, +): SafeParseResult { + return safeParse( + jsonString, + (x) => GetEntityUsageAlert$inboundSchema.parse(JSON.parse(x)), + `Failed to parse 'GetEntityUsageAlert' from JSON`, + ); +} + /** @internal */ export const GetEntityBillingControls$inboundSchema: z.ZodMiniType< GetEntityBillingControls, @@ -597,10 +672,14 @@ export const GetEntityBillingControls$inboundSchema: z.ZodMiniType< spend_limits: types.optional( z.array(z.lazy(() => GetEntitySpendLimit$inboundSchema)), ), + usage_alerts: types.optional( + z.array(z.lazy(() => GetEntityUsageAlert$inboundSchema)), + ), }), z.transform((v) => { return remap$(v, { "spend_limits": "spendLimits", + "usage_alerts": "usageAlerts", }); }), ); diff --git a/packages/sdk/src/models/get-or-create-customer-op.ts b/packages/sdk/src/models/get-or-create-customer-op.ts index 0d7c5de1a..67fd8b56b 100644 --- a/packages/sdk/src/models/get-or-create-customer-op.ts +++ b/packages/sdk/src/models/get-or-create-customer-op.ts @@ -82,6 +82,43 @@ export type GetOrCreateCustomerSpendLimit = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const GetOrCreateCustomerThresholdType = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type GetOrCreateCustomerThresholdType = ClosedEnum< + typeof GetOrCreateCustomerThresholdType +>; + +export type GetOrCreateCustomerUsageAlert = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled?: boolean | undefined; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: GetOrCreateCustomerThresholdType; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls for the customer (auto top-ups, etc.) */ @@ -94,6 +131,10 @@ export type GetOrCreateCustomerBillingControls = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; export type GetOrCreateCustomerParams = { @@ -255,10 +296,55 @@ export function getOrCreateCustomerSpendLimitToJSON( ); } +/** @internal */ +export const GetOrCreateCustomerThresholdType$outboundSchema: z.ZodMiniEnum< + typeof GetOrCreateCustomerThresholdType +> = z.enum(GetOrCreateCustomerThresholdType); + +/** @internal */ +export type GetOrCreateCustomerUsageAlert$Outbound = { + feature_id?: string | undefined; + enabled: boolean; + threshold: number; + threshold_type: string; + name?: string | undefined; +}; + +/** @internal */ +export const GetOrCreateCustomerUsageAlert$outboundSchema: z.ZodMiniType< + GetOrCreateCustomerUsageAlert$Outbound, + GetOrCreateCustomerUsageAlert +> = z.pipe( + z.object({ + featureId: z.optional(z.string()), + enabled: z._default(z.boolean(), true), + threshold: z.number(), + thresholdType: GetOrCreateCustomerThresholdType$outboundSchema, + name: z.optional(z.string()), + }), + z.transform((v) => { + return remap$(v, { + featureId: "feature_id", + thresholdType: "threshold_type", + }); + }), +); + +export function getOrCreateCustomerUsageAlertToJSON( + getOrCreateCustomerUsageAlert: GetOrCreateCustomerUsageAlert, +): string { + return JSON.stringify( + GetOrCreateCustomerUsageAlert$outboundSchema.parse( + getOrCreateCustomerUsageAlert, + ), + ); +} + /** @internal */ export type GetOrCreateCustomerBillingControls$Outbound = { auto_topups?: Array | undefined; spend_limits?: Array | undefined; + usage_alerts?: Array | undefined; }; /** @internal */ @@ -273,11 +359,15 @@ export const GetOrCreateCustomerBillingControls$outboundSchema: z.ZodMiniType< spendLimits: z.optional( z.array(z.lazy(() => GetOrCreateCustomerSpendLimit$outboundSchema)), ), + usageAlerts: z.optional( + z.array(z.lazy(() => GetOrCreateCustomerUsageAlert$outboundSchema)), + ), }), z.transform((v) => { return remap$(v, { autoTopups: "auto_topups", spendLimits: "spend_limits", + usageAlerts: "usage_alerts", }); }), ); diff --git a/packages/sdk/src/models/list-customers-op.ts b/packages/sdk/src/models/list-customers-op.ts index ffe79c531..7c1761444 100644 --- a/packages/sdk/src/models/list-customers-op.ts +++ b/packages/sdk/src/models/list-customers-op.ts @@ -139,6 +139,43 @@ export type ListCustomersSpendLimit = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const ListCustomersThresholdType = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type ListCustomersThresholdType = OpenEnum< + typeof ListCustomersThresholdType +>; + +export type ListCustomersUsageAlert = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled: boolean; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: ListCustomersThresholdType; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls for the customer (auto top-ups, etc.) */ @@ -151,6 +188,10 @@ export type ListCustomersBillingControls = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; /** @@ -588,6 +629,42 @@ export function listCustomersSpendLimitFromJSON( ); } +/** @internal */ +export const ListCustomersThresholdType$inboundSchema: z.ZodMiniType< + ListCustomersThresholdType, + unknown +> = openEnums.inboundSchema(ListCustomersThresholdType); + +/** @internal */ +export const ListCustomersUsageAlert$inboundSchema: z.ZodMiniType< + ListCustomersUsageAlert, + unknown +> = z.pipe( + z.object({ + feature_id: types.optional(types.string()), + enabled: z._default(types.boolean(), true), + threshold: types.number(), + threshold_type: ListCustomersThresholdType$inboundSchema, + name: types.optional(types.string()), + }), + z.transform((v) => { + return remap$(v, { + "feature_id": "featureId", + "threshold_type": "thresholdType", + }); + }), +); + +export function listCustomersUsageAlertFromJSON( + jsonString: string, +): SafeParseResult { + return safeParse( + jsonString, + (x) => ListCustomersUsageAlert$inboundSchema.parse(JSON.parse(x)), + `Failed to parse 'ListCustomersUsageAlert' from JSON`, + ); +} + /** @internal */ export const ListCustomersBillingControls$inboundSchema: z.ZodMiniType< ListCustomersBillingControls, @@ -600,11 +677,15 @@ export const ListCustomersBillingControls$inboundSchema: z.ZodMiniType< spend_limits: types.optional( z.array(z.lazy(() => ListCustomersSpendLimit$inboundSchema)), ), + usage_alerts: types.optional( + z.array(z.lazy(() => ListCustomersUsageAlert$inboundSchema)), + ), }), z.transform((v) => { return remap$(v, { "auto_topups": "autoTopups", "spend_limits": "spendLimits", + "usage_alerts": "usageAlerts", }); }), ); diff --git a/packages/sdk/src/models/multi-attach-op.ts b/packages/sdk/src/models/multi-attach-op.ts index 637e66fa7..e7b052550 100644 --- a/packages/sdk/src/models/multi-attach-op.ts +++ b/packages/sdk/src/models/multi-attach-op.ts @@ -434,6 +434,43 @@ export type MultiAttachSpendLimit = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const MultiAttachThresholdType = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type MultiAttachThresholdType = ClosedEnum< + typeof MultiAttachThresholdType +>; + +export type MultiAttachUsageAlert = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled?: boolean | undefined; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: MultiAttachThresholdType; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls for the entity. */ @@ -442,6 +479,10 @@ export type MultiAttachBillingControls = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; export type MultiAttachEntityData = { @@ -1116,9 +1157,52 @@ export function multiAttachSpendLimitToJSON( ); } +/** @internal */ +export const MultiAttachThresholdType$outboundSchema: z.ZodMiniEnum< + typeof MultiAttachThresholdType +> = z.enum(MultiAttachThresholdType); + +/** @internal */ +export type MultiAttachUsageAlert$Outbound = { + feature_id?: string | undefined; + enabled: boolean; + threshold: number; + threshold_type: string; + name?: string | undefined; +}; + +/** @internal */ +export const MultiAttachUsageAlert$outboundSchema: z.ZodMiniType< + MultiAttachUsageAlert$Outbound, + MultiAttachUsageAlert +> = z.pipe( + z.object({ + featureId: z.optional(z.string()), + enabled: z._default(z.boolean(), true), + threshold: z.number(), + thresholdType: MultiAttachThresholdType$outboundSchema, + name: z.optional(z.string()), + }), + z.transform((v) => { + return remap$(v, { + featureId: "feature_id", + thresholdType: "threshold_type", + }); + }), +); + +export function multiAttachUsageAlertToJSON( + multiAttachUsageAlert: MultiAttachUsageAlert, +): string { + return JSON.stringify( + MultiAttachUsageAlert$outboundSchema.parse(multiAttachUsageAlert), + ); +} + /** @internal */ export type MultiAttachBillingControls$Outbound = { spend_limits?: Array | undefined; + usage_alerts?: Array | undefined; }; /** @internal */ @@ -1130,10 +1214,14 @@ export const MultiAttachBillingControls$outboundSchema: z.ZodMiniType< spendLimits: z.optional( z.array(z.lazy(() => MultiAttachSpendLimit$outboundSchema)), ), + usageAlerts: z.optional( + z.array(z.lazy(() => MultiAttachUsageAlert$outboundSchema)), + ), }), z.transform((v) => { return remap$(v, { spendLimits: "spend_limits", + usageAlerts: "usage_alerts", }); }), ); diff --git a/packages/sdk/src/models/preview-attach-op.ts b/packages/sdk/src/models/preview-attach-op.ts index f0a16ec5a..69b603db9 100644 --- a/packages/sdk/src/models/preview-attach-op.ts +++ b/packages/sdk/src/models/preview-attach-op.ts @@ -749,6 +749,14 @@ export type PreviewAttachIncoming = { * When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. */ effectiveAt: number | null; + /** + * When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + */ + canceledAt: number | null; + /** + * When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + */ + expiresAt: number | null; }; export type PreviewAttachOutgoingFeatureQuantity = { @@ -776,6 +784,14 @@ export type PreviewAttachOutgoing = { * When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. */ effectiveAt: number | null; + /** + * When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + */ + canceledAt: number | null; + /** + * When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + */ + expiresAt: number | null; }; /** @@ -1803,12 +1819,16 @@ export const PreviewAttachIncoming$inboundSchema: z.ZodMiniType< z.lazy(() => PreviewAttachIncomingFeatureQuantity$inboundSchema), ), effective_at: types.nullable(types.number()), + canceled_at: types.nullable(types.number()), + expires_at: types.nullable(types.number()), }), z.transform((v) => { return remap$(v, { "plan_id": "planId", "feature_quantities": "featureQuantities", "effective_at": "effectiveAt", + "canceled_at": "canceledAt", + "expires_at": "expiresAt", }); }), ); @@ -1862,12 +1882,16 @@ export const PreviewAttachOutgoing$inboundSchema: z.ZodMiniType< z.lazy(() => PreviewAttachOutgoingFeatureQuantity$inboundSchema), ), effective_at: types.nullable(types.number()), + canceled_at: types.nullable(types.number()), + expires_at: types.nullable(types.number()), }), z.transform((v) => { return remap$(v, { "plan_id": "planId", "feature_quantities": "featureQuantities", "effective_at": "effectiveAt", + "canceled_at": "canceledAt", + "expires_at": "expiresAt", }); }), ); diff --git a/packages/sdk/src/models/preview-multi-attach-op.ts b/packages/sdk/src/models/preview-multi-attach-op.ts index 84255104a..16920a58e 100644 --- a/packages/sdk/src/models/preview-multi-attach-op.ts +++ b/packages/sdk/src/models/preview-multi-attach-op.ts @@ -438,6 +438,43 @@ export type PreviewMultiAttachSpendLimit = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const PreviewMultiAttachThresholdType = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type PreviewMultiAttachThresholdType = ClosedEnum< + typeof PreviewMultiAttachThresholdType +>; + +export type PreviewMultiAttachUsageAlert = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled?: boolean | undefined; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: PreviewMultiAttachThresholdType; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls for the entity. */ @@ -446,6 +483,10 @@ export type PreviewMultiAttachBillingControls = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; export type PreviewMultiAttachEntityData = { @@ -715,6 +756,14 @@ export type PreviewMultiAttachIncoming = { * When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. */ effectiveAt: number | null; + /** + * When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + */ + canceledAt: number | null; + /** + * When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + */ + expiresAt: number | null; }; export type PreviewMultiAttachOutgoingFeatureQuantity = { @@ -742,6 +791,14 @@ export type PreviewMultiAttachOutgoing = { * When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. */ effectiveAt: number | null; + /** + * When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + */ + canceledAt: number | null; + /** + * When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + */ + expiresAt: number | null; }; /** @@ -1358,9 +1415,54 @@ export function previewMultiAttachSpendLimitToJSON( ); } +/** @internal */ +export const PreviewMultiAttachThresholdType$outboundSchema: z.ZodMiniEnum< + typeof PreviewMultiAttachThresholdType +> = z.enum(PreviewMultiAttachThresholdType); + +/** @internal */ +export type PreviewMultiAttachUsageAlert$Outbound = { + feature_id?: string | undefined; + enabled: boolean; + threshold: number; + threshold_type: string; + name?: string | undefined; +}; + +/** @internal */ +export const PreviewMultiAttachUsageAlert$outboundSchema: z.ZodMiniType< + PreviewMultiAttachUsageAlert$Outbound, + PreviewMultiAttachUsageAlert +> = z.pipe( + z.object({ + featureId: z.optional(z.string()), + enabled: z._default(z.boolean(), true), + threshold: z.number(), + thresholdType: PreviewMultiAttachThresholdType$outboundSchema, + name: z.optional(z.string()), + }), + z.transform((v) => { + return remap$(v, { + featureId: "feature_id", + thresholdType: "threshold_type", + }); + }), +); + +export function previewMultiAttachUsageAlertToJSON( + previewMultiAttachUsageAlert: PreviewMultiAttachUsageAlert, +): string { + return JSON.stringify( + PreviewMultiAttachUsageAlert$outboundSchema.parse( + previewMultiAttachUsageAlert, + ), + ); +} + /** @internal */ export type PreviewMultiAttachBillingControls$Outbound = { spend_limits?: Array | undefined; + usage_alerts?: Array | undefined; }; /** @internal */ @@ -1372,10 +1474,14 @@ export const PreviewMultiAttachBillingControls$outboundSchema: z.ZodMiniType< spendLimits: z.optional( z.array(z.lazy(() => PreviewMultiAttachSpendLimit$outboundSchema)), ), + usageAlerts: z.optional( + z.array(z.lazy(() => PreviewMultiAttachUsageAlert$outboundSchema)), + ), }), z.transform((v) => { return remap$(v, { spendLimits: "spend_limits", + usageAlerts: "usage_alerts", }); }), ); @@ -1812,12 +1918,16 @@ export const PreviewMultiAttachIncoming$inboundSchema: z.ZodMiniType< z.lazy(() => PreviewMultiAttachIncomingFeatureQuantity$inboundSchema), ), effective_at: types.nullable(types.number()), + canceled_at: types.nullable(types.number()), + expires_at: types.nullable(types.number()), }), z.transform((v) => { return remap$(v, { "plan_id": "planId", "feature_quantities": "featureQuantities", "effective_at": "effectiveAt", + "canceled_at": "canceledAt", + "expires_at": "expiresAt", }); }), ); @@ -1874,12 +1984,16 @@ export const PreviewMultiAttachOutgoing$inboundSchema: z.ZodMiniType< z.lazy(() => PreviewMultiAttachOutgoingFeatureQuantity$inboundSchema), ), effective_at: types.nullable(types.number()), + canceled_at: types.nullable(types.number()), + expires_at: types.nullable(types.number()), }), z.transform((v) => { return remap$(v, { "plan_id": "planId", "feature_quantities": "featureQuantities", "effective_at": "effectiveAt", + "canceled_at": "canceledAt", + "expires_at": "expiresAt", }); }), ); diff --git a/packages/sdk/src/models/preview-update-op.ts b/packages/sdk/src/models/preview-update-op.ts index bc3526adf..c6884ba5f 100644 --- a/packages/sdk/src/models/preview-update-op.ts +++ b/packages/sdk/src/models/preview-update-op.ts @@ -415,6 +415,16 @@ export type PreviewUpdateCancelAction = ClosedEnum< typeof PreviewUpdateCancelAction >; +/** + * Controls whether balances should be recalculated during the subscription update. + */ +export type PreviewUpdateRecalculateBalances = { + /** + * If true, recalculates balances during the subscription update. Only applicable when updating feature quantities. + */ + enabled: boolean; +}; + export type PreviewUpdateParams = { /** * The ID of the customer to attach the plan to. @@ -464,6 +474,10 @@ export type PreviewUpdateParams = { * If true, the subscription is updated internally without applying billing changes in Stripe. */ noBillingChanges?: boolean | undefined; + /** + * Controls whether balances should be recalculated during the subscription update. + */ + recalculateBalances?: PreviewUpdateRecalculateBalances | undefined; }; export type PreviewUpdateDiscount = { @@ -670,6 +684,14 @@ export type PreviewUpdateIncoming = { * When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. */ effectiveAt: number | null; + /** + * When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + */ + canceledAt: number | null; + /** + * When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + */ + expiresAt: number | null; }; export type PreviewUpdateOutgoingFeatureQuantity = { @@ -697,6 +719,14 @@ export type PreviewUpdateOutgoing = { * When this change takes effect, in milliseconds since the Unix epoch, or null if it applies immediately. */ effectiveAt: number | null; + /** + * When this plan was canceled, in milliseconds since the Unix epoch, or null if it is not canceled. + */ + canceledAt: number | null; + /** + * When this plan expires, in milliseconds since the Unix epoch, or null if it does not expire. + */ + expiresAt: number | null; }; export const Intent = { @@ -1211,6 +1241,29 @@ export const PreviewUpdateCancelAction$outboundSchema: z.ZodMiniEnum< typeof PreviewUpdateCancelAction > = z.enum(PreviewUpdateCancelAction); +/** @internal */ +export type PreviewUpdateRecalculateBalances$Outbound = { + enabled: boolean; +}; + +/** @internal */ +export const PreviewUpdateRecalculateBalances$outboundSchema: z.ZodMiniType< + PreviewUpdateRecalculateBalances$Outbound, + PreviewUpdateRecalculateBalances +> = z.object({ + enabled: z.boolean(), +}); + +export function previewUpdateRecalculateBalancesToJSON( + previewUpdateRecalculateBalances: PreviewUpdateRecalculateBalances, +): string { + return JSON.stringify( + PreviewUpdateRecalculateBalances$outboundSchema.parse( + previewUpdateRecalculateBalances, + ), + ); +} + /** @internal */ export type PreviewUpdateParams$Outbound = { customer_id: string; @@ -1227,6 +1280,7 @@ export type PreviewUpdateParams$Outbound = { subscription_id?: string | undefined; cancel_action?: string | undefined; no_billing_changes?: boolean | undefined; + recalculate_balances?: PreviewUpdateRecalculateBalances$Outbound | undefined; }; /** @internal */ @@ -1256,6 +1310,9 @@ export const PreviewUpdateParams$outboundSchema: z.ZodMiniType< subscriptionId: z.optional(z.string()), cancelAction: z.optional(PreviewUpdateCancelAction$outboundSchema), noBillingChanges: z.optional(z.boolean()), + recalculateBalances: z.optional( + z.lazy(() => PreviewUpdateRecalculateBalances$outboundSchema), + ), }), z.transform((v) => { return remap$(v, { @@ -1269,6 +1326,7 @@ export const PreviewUpdateParams$outboundSchema: z.ZodMiniType< subscriptionId: "subscription_id", cancelAction: "cancel_action", noBillingChanges: "no_billing_changes", + recalculateBalances: "recalculate_balances", }); }), ); @@ -1585,12 +1643,16 @@ export const PreviewUpdateIncoming$inboundSchema: z.ZodMiniType< z.lazy(() => PreviewUpdateIncomingFeatureQuantity$inboundSchema), ), effective_at: types.nullable(types.number()), + canceled_at: types.nullable(types.number()), + expires_at: types.nullable(types.number()), }), z.transform((v) => { return remap$(v, { "plan_id": "planId", "feature_quantities": "featureQuantities", "effective_at": "effectiveAt", + "canceled_at": "canceledAt", + "expires_at": "expiresAt", }); }), ); @@ -1644,12 +1706,16 @@ export const PreviewUpdateOutgoing$inboundSchema: z.ZodMiniType< z.lazy(() => PreviewUpdateOutgoingFeatureQuantity$inboundSchema), ), effective_at: types.nullable(types.number()), + canceled_at: types.nullable(types.number()), + expires_at: types.nullable(types.number()), }), z.transform((v) => { return remap$(v, { "plan_id": "planId", "feature_quantities": "featureQuantities", "effective_at": "effectiveAt", + "canceled_at": "canceledAt", + "expires_at": "expiresAt", }); }), ); diff --git a/packages/sdk/src/models/update-customer-op.ts b/packages/sdk/src/models/update-customer-op.ts index bcd66af95..64d04d0ee 100644 --- a/packages/sdk/src/models/update-customer-op.ts +++ b/packages/sdk/src/models/update-customer-op.ts @@ -89,6 +89,43 @@ export type UpdateCustomerSpendLimitRequest = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const UpdateCustomerThresholdTypeRequestBody = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type UpdateCustomerThresholdTypeRequestBody = ClosedEnum< + typeof UpdateCustomerThresholdTypeRequestBody +>; + +export type UpdateCustomerUsageAlertRequestBody = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled?: boolean | undefined; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: UpdateCustomerThresholdTypeRequestBody; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls for the customer (auto top-ups, etc.) */ @@ -101,6 +138,10 @@ export type UpdateCustomerBillingControlsRequest = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; export type UpdateCustomerParams = { @@ -226,6 +267,43 @@ export type UpdateCustomerSpendLimitResponse = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const UpdateCustomerThresholdTypeResponse = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type UpdateCustomerThresholdTypeResponse = OpenEnum< + typeof UpdateCustomerThresholdTypeResponse +>; + +export type UpdateCustomerUsageAlertResponse = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled: boolean; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: UpdateCustomerThresholdTypeResponse; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls for the customer (auto top-ups, etc.) */ @@ -238,6 +316,10 @@ export type UpdateCustomerBillingControlsResponse = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; /** @@ -604,10 +686,58 @@ export function updateCustomerSpendLimitRequestToJSON( ); } +/** @internal */ +export const UpdateCustomerThresholdTypeRequestBody$outboundSchema: + z.ZodMiniEnum = z.enum( + UpdateCustomerThresholdTypeRequestBody, + ); + +/** @internal */ +export type UpdateCustomerUsageAlertRequestBody$Outbound = { + feature_id?: string | undefined; + enabled: boolean; + threshold: number; + threshold_type: string; + name?: string | undefined; +}; + +/** @internal */ +export const UpdateCustomerUsageAlertRequestBody$outboundSchema: z.ZodMiniType< + UpdateCustomerUsageAlertRequestBody$Outbound, + UpdateCustomerUsageAlertRequestBody +> = z.pipe( + z.object({ + featureId: z.optional(z.string()), + enabled: z._default(z.boolean(), true), + threshold: z.number(), + thresholdType: UpdateCustomerThresholdTypeRequestBody$outboundSchema, + name: z.optional(z.string()), + }), + z.transform((v) => { + return remap$(v, { + featureId: "feature_id", + thresholdType: "threshold_type", + }); + }), +); + +export function updateCustomerUsageAlertRequestBodyToJSON( + updateCustomerUsageAlertRequestBody: UpdateCustomerUsageAlertRequestBody, +): string { + return JSON.stringify( + UpdateCustomerUsageAlertRequestBody$outboundSchema.parse( + updateCustomerUsageAlertRequestBody, + ), + ); +} + /** @internal */ export type UpdateCustomerBillingControlsRequest$Outbound = { auto_topups?: Array | undefined; spend_limits?: Array | undefined; + usage_alerts?: + | Array + | undefined; }; /** @internal */ @@ -622,11 +752,15 @@ export const UpdateCustomerBillingControlsRequest$outboundSchema: z.ZodMiniType< spendLimits: z.optional( z.array(z.lazy(() => UpdateCustomerSpendLimitRequest$outboundSchema)), ), + usageAlerts: z.optional( + z.array(z.lazy(() => UpdateCustomerUsageAlertRequestBody$outboundSchema)), + ), }), z.transform((v) => { return remap$(v, { autoTopups: "auto_topups", spendLimits: "spend_limits", + usageAlerts: "usage_alerts", }); }), ); @@ -791,6 +925,42 @@ export function updateCustomerSpendLimitResponseFromJSON( ); } +/** @internal */ +export const UpdateCustomerThresholdTypeResponse$inboundSchema: z.ZodMiniType< + UpdateCustomerThresholdTypeResponse, + unknown +> = openEnums.inboundSchema(UpdateCustomerThresholdTypeResponse); + +/** @internal */ +export const UpdateCustomerUsageAlertResponse$inboundSchema: z.ZodMiniType< + UpdateCustomerUsageAlertResponse, + unknown +> = z.pipe( + z.object({ + feature_id: types.optional(types.string()), + enabled: z._default(types.boolean(), true), + threshold: types.number(), + threshold_type: UpdateCustomerThresholdTypeResponse$inboundSchema, + name: types.optional(types.string()), + }), + z.transform((v) => { + return remap$(v, { + "feature_id": "featureId", + "threshold_type": "thresholdType", + }); + }), +); + +export function updateCustomerUsageAlertResponseFromJSON( + jsonString: string, +): SafeParseResult { + return safeParse( + jsonString, + (x) => UpdateCustomerUsageAlertResponse$inboundSchema.parse(JSON.parse(x)), + `Failed to parse 'UpdateCustomerUsageAlertResponse' from JSON`, + ); +} + /** @internal */ export const UpdateCustomerBillingControlsResponse$inboundSchema: z.ZodMiniType< UpdateCustomerBillingControlsResponse, @@ -803,11 +973,15 @@ export const UpdateCustomerBillingControlsResponse$inboundSchema: z.ZodMiniType< spend_limits: types.optional( z.array(z.lazy(() => UpdateCustomerSpendLimitResponse$inboundSchema)), ), + usage_alerts: types.optional( + z.array(z.lazy(() => UpdateCustomerUsageAlertResponse$inboundSchema)), + ), }), z.transform((v) => { return remap$(v, { "auto_topups": "autoTopups", "spend_limits": "spendLimits", + "usage_alerts": "usageAlerts", }); }), ); diff --git a/packages/sdk/src/models/update-entity-op.ts b/packages/sdk/src/models/update-entity-op.ts index 1761c6366..a3974d0fb 100644 --- a/packages/sdk/src/models/update-entity-op.ts +++ b/packages/sdk/src/models/update-entity-op.ts @@ -6,7 +6,7 @@ import * as z from "zod/v4-mini"; import { remap as remap$ } from "../lib/primitives.js"; import { safeParse } from "../lib/schemas.js"; import * as openEnums from "../types/enums.js"; -import { OpenEnum } from "../types/enums.js"; +import { ClosedEnum, OpenEnum } from "../types/enums.js"; import { Result as SafeParseResult } from "../types/fp.js"; import * as types from "../types/primitives.js"; import { Balance, Balance$inboundSchema } from "./balance.js"; @@ -32,6 +32,43 @@ export type UpdateEntitySpendLimitRequest = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const UpdateEntityThresholdTypeRequestBody = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type UpdateEntityThresholdTypeRequestBody = ClosedEnum< + typeof UpdateEntityThresholdTypeRequestBody +>; + +export type UpdateEntityUsageAlertRequestBody = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled?: boolean | undefined; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: UpdateEntityThresholdTypeRequestBody; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls to replace on the entity. */ @@ -40,6 +77,10 @@ export type UpdateEntityBillingControlsRequest = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; export type UpdateEntityParams = { @@ -271,6 +312,43 @@ export type UpdateEntitySpendLimitResponse = { overageLimit?: number | undefined; }; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export const UpdateEntityThresholdTypeResponse = { + Usage: "usage", + UsagePercentage: "usage_percentage", +} as const; +/** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ +export type UpdateEntityThresholdTypeResponse = OpenEnum< + typeof UpdateEntityThresholdTypeResponse +>; + +export type UpdateEntityUsageAlertResponse = { + /** + * The feature ID this alert applies to. If omitted, the alert applies globally. + */ + featureId?: string | undefined; + /** + * Whether this usage alert is enabled. + */ + enabled: boolean; + /** + * The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100). + */ + threshold: number; + /** + * Whether the threshold is an absolute usage count or a percentage of the usage allowance. + */ + thresholdType: UpdateEntityThresholdTypeResponse; + /** + * Optional user-defined label to distinguish multiple alerts on the same feature. + */ + name?: string | undefined; +}; + /** * Billing controls for the entity. */ @@ -279,6 +357,10 @@ export type UpdateEntityBillingControlsResponse = { * List of overage spend limits per feature. */ spendLimits?: Array | undefined; + /** + * List of usage alert configurations per feature. + */ + usageAlerts?: Array | undefined; }; export type UpdateEntityInvoice = { @@ -389,9 +471,54 @@ export function updateEntitySpendLimitRequestToJSON( ); } +/** @internal */ +export const UpdateEntityThresholdTypeRequestBody$outboundSchema: z.ZodMiniEnum< + typeof UpdateEntityThresholdTypeRequestBody +> = z.enum(UpdateEntityThresholdTypeRequestBody); + +/** @internal */ +export type UpdateEntityUsageAlertRequestBody$Outbound = { + feature_id?: string | undefined; + enabled: boolean; + threshold: number; + threshold_type: string; + name?: string | undefined; +}; + +/** @internal */ +export const UpdateEntityUsageAlertRequestBody$outboundSchema: z.ZodMiniType< + UpdateEntityUsageAlertRequestBody$Outbound, + UpdateEntityUsageAlertRequestBody +> = z.pipe( + z.object({ + featureId: z.optional(z.string()), + enabled: z._default(z.boolean(), true), + threshold: z.number(), + thresholdType: UpdateEntityThresholdTypeRequestBody$outboundSchema, + name: z.optional(z.string()), + }), + z.transform((v) => { + return remap$(v, { + featureId: "feature_id", + thresholdType: "threshold_type", + }); + }), +); + +export function updateEntityUsageAlertRequestBodyToJSON( + updateEntityUsageAlertRequestBody: UpdateEntityUsageAlertRequestBody, +): string { + return JSON.stringify( + UpdateEntityUsageAlertRequestBody$outboundSchema.parse( + updateEntityUsageAlertRequestBody, + ), + ); +} + /** @internal */ export type UpdateEntityBillingControlsRequest$Outbound = { spend_limits?: Array | undefined; + usage_alerts?: Array | undefined; }; /** @internal */ @@ -403,10 +530,14 @@ export const UpdateEntityBillingControlsRequest$outboundSchema: z.ZodMiniType< spendLimits: z.optional( z.array(z.lazy(() => UpdateEntitySpendLimitRequest$outboundSchema)), ), + usageAlerts: z.optional( + z.array(z.lazy(() => UpdateEntityUsageAlertRequestBody$outboundSchema)), + ), }), z.transform((v) => { return remap$(v, { spendLimits: "spend_limits", + usageAlerts: "usage_alerts", }); }), ); @@ -693,6 +824,42 @@ export function updateEntitySpendLimitResponseFromJSON( ); } +/** @internal */ +export const UpdateEntityThresholdTypeResponse$inboundSchema: z.ZodMiniType< + UpdateEntityThresholdTypeResponse, + unknown +> = openEnums.inboundSchema(UpdateEntityThresholdTypeResponse); + +/** @internal */ +export const UpdateEntityUsageAlertResponse$inboundSchema: z.ZodMiniType< + UpdateEntityUsageAlertResponse, + unknown +> = z.pipe( + z.object({ + feature_id: types.optional(types.string()), + enabled: z._default(types.boolean(), true), + threshold: types.number(), + threshold_type: UpdateEntityThresholdTypeResponse$inboundSchema, + name: types.optional(types.string()), + }), + z.transform((v) => { + return remap$(v, { + "feature_id": "featureId", + "threshold_type": "thresholdType", + }); + }), +); + +export function updateEntityUsageAlertResponseFromJSON( + jsonString: string, +): SafeParseResult { + return safeParse( + jsonString, + (x) => UpdateEntityUsageAlertResponse$inboundSchema.parse(JSON.parse(x)), + `Failed to parse 'UpdateEntityUsageAlertResponse' from JSON`, + ); +} + /** @internal */ export const UpdateEntityBillingControlsResponse$inboundSchema: z.ZodMiniType< UpdateEntityBillingControlsResponse, @@ -702,10 +869,14 @@ export const UpdateEntityBillingControlsResponse$inboundSchema: z.ZodMiniType< spend_limits: types.optional( z.array(z.lazy(() => UpdateEntitySpendLimitResponse$inboundSchema)), ), + usage_alerts: types.optional( + z.array(z.lazy(() => UpdateEntityUsageAlertResponse$inboundSchema)), + ), }), z.transform((v) => { return remap$(v, { "spend_limits": "spendLimits", + "usage_alerts": "usageAlerts", }); }), ); diff --git a/packages/sdk/src/sdk/billing.ts b/packages/sdk/src/sdk/billing.ts index 28e980d78..7891ff9bc 100644 --- a/packages/sdk/src/sdk/billing.ts +++ b/packages/sdk/src/sdk/billing.ts @@ -232,6 +232,7 @@ export class Billing extends ClientSDK { * @param subscriptionId - A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. (optional) * @param cancelAction - Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. (optional) * @param noBillingChanges - If true, the subscription is updated internally without applying billing changes in Stripe. (optional) + * @param recalculateBalances - Controls whether balances should be recalculated during the subscription update. (optional) * * @returns A billing response with customer ID, invoice details, and payment URL (if next action is required). */ @@ -269,6 +270,7 @@ export class Billing extends ClientSDK { * @param subscriptionId - A unique ID to identify this subscription. Can be used to target specific subscriptions in update operations when a customer has multiple products with the same plan. (optional) * @param cancelAction - Action to perform for cancellation. 'cancel_immediately' cancels now with prorated refund, 'cancel_end_of_cycle' cancels at period end, 'uncancel' reverses a pending cancellation. (optional) * @param noBillingChanges - If true, the subscription is updated internally without applying billing changes in Stripe. (optional) + * @param recalculateBalances - Controls whether balances should be recalculated during the subscription update. (optional) * * @returns A preview response with line items showing prorated charges or credits for the proposed changes. */ diff --git a/packages/sdk/tsconfig.json b/packages/sdk/tsconfig.json index 76110f883..0ccdbe286 100644 --- a/packages/sdk/tsconfig.json +++ b/packages/sdk/tsconfig.json @@ -13,7 +13,6 @@ "declaration": true, "declarationMap": true, "sourceMap": true, - "rootDir": "src", "outDir": ".", @@ -37,5 +36,5 @@ "forceConsistentCasingInFileNames": true }, "include": ["src"], - "exclude": ["node_modules", "src/__tests__"] + "exclude": ["node_modules"] } diff --git a/server/src/_luaScriptsV2/customers/updateCustomerData.lua b/server/src/_luaScriptsV2/customers/updateCustomerData.lua index 570acb58d..9b4762908 100644 --- a/server/src/_luaScriptsV2/customers/updateCustomerData.lua +++ b/server/src/_luaScriptsV2/customers/updateCustomerData.lua @@ -17,7 +17,8 @@ processor?: object | null, processors?: object | null, auto_topups?: array | null, - spend_limits?: array | null + spend_limits?: array | null, + usage_alerts?: array | null } } @@ -111,4 +112,13 @@ if updates.spend_limits ~= nil then table.insert(updated_fields, 'spend_limits') end +if updates.usage_alerts ~= nil then + if is_nil(updates.usage_alerts) then + redis.call('JSON.SET', cache_key, '$.usage_alerts', 'null') + else + redis.call('JSON.SET', cache_key, '$.usage_alerts', cjson.encode(updates.usage_alerts)) + end + table.insert(updated_fields, 'usage_alerts') +end + return cjson.encode({ success = true, updated_fields = updated_fields }) diff --git a/server/src/external/autumn/autumnWebhookRouter.ts b/server/src/external/autumn/autumnWebhookRouter.ts index 185a21004..a5f111081 100644 --- a/server/src/external/autumn/autumnWebhookRouter.ts +++ b/server/src/external/autumn/autumnWebhookRouter.ts @@ -1,4 +1,4 @@ -import { ErrCode, WebhookEventType } from "@autumn/shared"; +import { ErrCode } from "@autumn/shared"; import { Hono } from "hono"; import { Webhook } from "svix"; import type { HonoEnv } from "@/honoUtils/HonoEnv.js"; @@ -58,17 +58,19 @@ autumnWebhookRouter.post("", async (c) => { console.log("Received webhook from autumn"); const { type, data } = evt; - switch (type) { - case WebhookEventType.CustomerProductsUpdated: - console.log( - `Type: ${type}, Scenario: ${data?.scenario}, Product: ${(data?.updated_product as { id?: string })?.id}`, - ); - break; - case WebhookEventType.CustomerThresholdReached: - console.log(`Type: ${type}`); - console.log(`Feature: `, data?.feature); - break; - } + console.log("EVENT:", JSON.stringify(evt, null, 2)); + + // switch (type) { + // case WebhookEventType.CustomerProductsUpdated: + // console.log( + // `Type: ${type}, Scenario: ${data?.scenario}, Product: ${(data?.updated_product as { id?: string })?.id}`, + // ); + // break; + // case WebhookEventType.CustomerThresholdReached: + // console.log(`Type: ${type}`); + // console.log(`Feature: `, data?.feature); + // break; + // } return c.json({ success: true, message: "Webhook received" }, 200); } catch (_error) { diff --git a/server/src/external/svix/svixHelpers.ts b/server/src/external/svix/svixHelpers.ts index c2724bf10..83302847e 100644 --- a/server/src/external/svix/svixHelpers.ts +++ b/server/src/external/svix/svixHelpers.ts @@ -1,4 +1,7 @@ import type { AppEnv, Organization } from "@autumn/shared"; +import * as Sentry from "@sentry/bun"; +import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; +import { getSentryTags } from "@/external/sentry/sentryUtils.js"; import { createSvixCli, getSvixAppId, safeSvix } from "./svixUtils.js"; export const createSvixApp = safeSvix({ @@ -11,7 +14,7 @@ export const createSvixApp = safeSvix({ name: string; orgId: string; env: AppEnv; - meta?: Record; + meta?: Record; }) => { const svix = createSvixCli(); const app = await svix.application.create({ @@ -35,23 +38,26 @@ export const deleteSvixApp = safeSvix({ action: "deleteSvixApp", }); -export const sendSvixEvent = safeSvix({ - fn: async ({ - org, - env, - eventType, - data, - }: { - org: Organization; - env: AppEnv; - eventType: string; - data: any; - }) => { +export const sendSvixEvent = async ({ + ctx, + eventType, + data, +}: { + ctx: AutumnContext; + eventType: string; + data: unknown; +}) => { + if (!process.env.SVIX_API_KEY) return; + + const { org, env } = ctx; + + try { + ctx.logger.info(`[svix] Firing webhook: ${eventType}`); + const svix = createSvixCli(); const appId = getSvixAppId({ org, env }); - if (!appId) { - return null; - } + if (!appId) return null; + return await svix.message.create(appId, { eventType, payload: { @@ -59,9 +65,13 @@ export const sendSvixEvent = safeSvix({ data, }, }); - }, - action: "sendSvixEvent", -}); + } catch (error) { + ctx.logger.error(`[svix] Failed to send ${eventType}: ${error}`); + Sentry.captureException(error, { + tags: getSentryTags({ ctx }), + }); + } +}; export const sendCustomSvixEvent = safeSvix({ fn: async ({ @@ -76,7 +86,7 @@ export const sendCustomSvixEvent = safeSvix({ org: Organization; env: AppEnv; eventType: string; - data: any; + data: unknown; appId: string; }) => { const svix = createSvixCli(); diff --git a/server/src/internal/analytics/handlers/handleProductsUpdated.ts b/server/src/internal/analytics/handlers/handleProductsUpdated.ts index 6e4205762..77ca1cecd 100644 --- a/server/src/internal/analytics/handlers/handleProductsUpdated.ts +++ b/server/src/internal/analytics/handlers/handleProductsUpdated.ts @@ -195,8 +195,7 @@ export const handleProductsUpdated = async ({ `sending customer.products.updated webhook, customer ID: ${data.customerId}, entity ID: ${fullCus.entity?.id || "none"}`, ); await sendSvixEvent({ - org, - env, + ctx, eventType: "customer.products.updated", data: { scenario, diff --git a/server/src/internal/api/check/checkUtils/getV2CheckResponse.ts b/server/src/internal/api/check/checkUtils/getV2CheckResponse.ts index 1538ee9a9..92c168fcd 100644 --- a/server/src/internal/api/check/checkUtils/getV2CheckResponse.ts +++ b/server/src/internal/api/check/checkUtils/getV2CheckResponse.ts @@ -54,7 +54,7 @@ export const getV2CheckResponse = async ({ apiSubject, feature: featureToUse, requiredBalance, - }) + }).allowed : false; return CheckResponseV3Schema.parse({ diff --git a/server/src/internal/balances/trackWebhooks/checkLimitReached.ts b/server/src/internal/balances/trackWebhooks/checkLimitReached.ts new file mode 100644 index 000000000..d04f0d35c --- /dev/null +++ b/server/src/internal/balances/trackWebhooks/checkLimitReached.ts @@ -0,0 +1,123 @@ +import { + type ApiCustomerV5, + type ApiEntityV2, + apiBalanceToAllowed, + type Feature, + type FullCustomer, + WebhookEventType, +} from "@autumn/shared"; +import { sendSvixEvent } from "@/external/svix/svixHelpers.js"; +import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; +import { getApiCustomerBase } from "@/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.js"; +import { getApiEntityBase } from "@/internal/entities/entityUtils/apiEntityUtils/getApiEntityBase.js"; + +const checkLimitForSubject = async ({ + ctx, + oldFullCus, + newFullCus, + feature, + entityId, +}: { + ctx: AutumnContext; + oldFullCus: FullCustomer; + newFullCus: FullCustomer; + feature: Feature; + entityId?: string; +}) => { + const entity = entityId + ? newFullCus.entities?.find((e) => e.id === entityId) + : undefined; + + let oldSubject: ApiCustomerV5 | ApiEntityV2 | undefined; + let newSubject: ApiCustomerV5 | ApiEntityV2 | undefined; + + if (entity) { + const { apiEntity: oldApiEntity } = await getApiEntityBase({ + ctx, + entity, + fullCus: oldFullCus, + }); + const { apiEntity: newApiEntity } = await getApiEntityBase({ + ctx, + entity, + fullCus: newFullCus, + }); + oldSubject = oldApiEntity; + newSubject = newApiEntity; + } else { + const { apiCustomer: oldApiCustomer } = await getApiCustomerBase({ + ctx, + fullCus: oldFullCus, + }); + const { apiCustomer: newApiCustomer } = await getApiCustomerBase({ + ctx, + fullCus: newFullCus, + }); + oldSubject = oldApiCustomer; + newSubject = newApiCustomer; + } + + const oldBalance = oldSubject.balances?.[feature.id]; + const newBalance = newSubject.balances?.[feature.id]; + + if (!oldBalance || !newBalance) return; + + const oldResult = apiBalanceToAllowed({ + apiBalance: oldBalance, + apiSubject: oldSubject, + feature, + requiredBalance: 0.0000001, + }); + + const newResult = apiBalanceToAllowed({ + apiBalance: newBalance, + apiSubject: newSubject, + feature, + requiredBalance: 0.0000001, + }); + + if (!oldResult.allowed || newResult.allowed) return; + + const customerId = newFullCus.id || newFullCus.internal_id; + + await sendSvixEvent({ + ctx, + eventType: WebhookEventType.BalancesLimitReached, + data: { + customer_id: customerId, + feature_id: feature.id, + limit_type: newResult.limitType ?? "included", + ...(entityId && { entity_id: entityId }), + }, + }); + + ctx.logger.info( + `Limit reached for customer ${customerId}, feature ${feature.id}, type ${newResult.limitType ?? "included"}${entityId ? `, entity ${entityId}` : ""}`, + ); +}; + +export const checkLimitReached = async ({ + ctx, + oldFullCus, + newFullCus, + feature, + entityId, +}: { + ctx: AutumnContext; + oldFullCus: FullCustomer; + newFullCus: FullCustomer; + feature: Feature; + entityId?: string; +}) => { + try { + await checkLimitForSubject({ + ctx, + oldFullCus, + newFullCus, + feature, + entityId, + }); + } catch (error) { + ctx.logger.error(`[checkLimitReached] error: ${error}`, { error }); + } +}; diff --git a/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts b/server/src/internal/balances/trackWebhooks/checkUsageAlerts.ts similarity index 70% rename from server/src/internal/balances/thresholdReached/checkUsageAlerts.ts rename to server/src/internal/balances/trackWebhooks/checkUsageAlerts.ts index 933f253c1..7002f7059 100644 --- a/server/src/internal/balances/thresholdReached/checkUsageAlerts.ts +++ b/server/src/internal/balances/trackWebhooks/checkUsageAlerts.ts @@ -25,11 +25,13 @@ const wasThresholdCrossed = ({ oldGrantedBalance: number; newGrantedBalance: number; }) => { - if (alert.threshold_type === "usage_threshold") { - return oldUsage < alert.threshold && newUsage >= alert.threshold; + if (alert.threshold_type === "usage") { + const shldAlert = oldUsage < alert.threshold && newUsage >= alert.threshold; + + return shldAlert; } - // usage_percentage_threshold + // usage_percentage if (oldGrantedBalance <= 0 || newGrantedBalance <= 0) return false; const oldPercentage = new Decimal(oldUsage) @@ -44,21 +46,27 @@ const wasThresholdCrossed = ({ return oldPercentage < alert.threshold && newPercentage >= alert.threshold; }; -export const checkUsageAlerts = async ({ +const processAlerts = async ({ ctx, oldFullCus, newFullCus, feature, + entityId, }: { ctx: AutumnContext; oldFullCus: FullCustomer; newFullCus: FullCustomer; feature: Feature; + entityId?: string; }) => { - const usageAlerts = newFullCus.usage_alerts; - if (!usageAlerts || usageAlerts.length === 0) return; + const entity = entityId + ? newFullCus.entities?.find((e) => e.id === entityId) + : undefined; + + const alerts = entity ? entity.usage_alerts : newFullCus.usage_alerts; + if (!alerts || alerts.length === 0) return; - const matchingAlerts = usageAlerts.filter( + const matchingAlerts = alerts.filter( (alert) => alert.enabled && (alert.feature_id === feature.id || !alert.feature_id), ); @@ -68,11 +76,13 @@ export const checkUsageAlerts = async ({ const oldCustomerEntitlements = fullCustomerToCustomerEntitlements({ fullCustomer: oldFullCus, featureId: feature.id, + entity, }); const newCustomerEntitlements = fullCustomerToCustomerEntitlements({ fullCustomer: newFullCus, featureId: feature.id, + entity, }); const oldUsage = cusEntsToUsage({ cusEnts: oldCustomerEntitlements }); @@ -105,13 +115,12 @@ export const checkUsageAlerts = async ({ const customerId = newFullCus.id || newFullCus.internal_id; await sendSvixEvent({ - org: ctx.org, - env: ctx.env, - eventType: WebhookEventType.BalancesThresholdReached, + ctx, + eventType: WebhookEventType.BalancesUsageAlertTriggered, data: { customer_id: customerId, feature_id: feature.id, - threshold_type: "usage_alert", + ...(entityId && { entity_id: entityId }), usage_alert: { name: alert.name, threshold: alert.threshold, @@ -121,7 +130,28 @@ export const checkUsageAlerts = async ({ }); ctx.logger.info( - `Usage alert triggered for customer ${customerId}, feature ${feature.id}, threshold ${alert.threshold} (${alert.threshold_type})`, + `Usage alert triggered for customer ${customerId}, feature ${feature.id}, threshold ${alert.threshold} (${alert.threshold_type})${entityId ? `, entity ${entityId}` : ""}`, ); } }; + +export const checkUsageAlerts = async ({ + ctx, + oldFullCus, + newFullCus, + feature, + entityId, +}: { + ctx: AutumnContext; + oldFullCus: FullCustomer; + newFullCus: FullCustomer; + feature: Feature; + entityId?: string; +}) => { + // 1. Customer-level alerts (always checked, no entity scoping) + await processAlerts({ ctx, oldFullCus, newFullCus, feature }); + + // 2. Entity-level alerts (only when entityId is provided) + if (!entityId) return; + await processAlerts({ ctx, oldFullCus, newFullCus, feature, entityId }); +}; diff --git a/server/src/internal/balances/trackWebhooks/fireTrackWebhooks.ts b/server/src/internal/balances/trackWebhooks/fireTrackWebhooks.ts new file mode 100644 index 000000000..cd91245a9 --- /dev/null +++ b/server/src/internal/balances/trackWebhooks/fireTrackWebhooks.ts @@ -0,0 +1,48 @@ +import type { Feature, FullCustomer } from "@autumn/shared"; +import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; +import { checkUsageAlerts } from "./checkUsageAlerts.js"; +import { handleThresholdReached } from "./handleThresholdReached.js"; +import { checkLimitReached } from "./checkLimitReached.js"; + +export const fireTrackWebhooks = ({ + ctx, + oldFullCus, + newFullCus, + feature, + entityId, +}: { + ctx: AutumnContext; + oldFullCus: FullCustomer; + newFullCus: FullCustomer; + feature: Feature; + entityId?: string; +}) => { + handleThresholdReached({ + ctx, + oldFullCus, + newFullCus, + feature, + }).catch((error) => { + ctx.logger.error(`[fireTrackWebhooks] handleThresholdReached: ${error}`); + }); + + checkUsageAlerts({ + ctx, + oldFullCus, + newFullCus, + feature, + entityId, + }).catch((error) => { + ctx.logger.error(`[fireTrackWebhooks] checkUsageAlerts: ${error}`); + }); + + checkLimitReached({ + ctx, + oldFullCus, + newFullCus, + feature, + entityId, + }).catch((error) => { + ctx.logger.error(`[fireTrackWebhooks] checkLimitReached: ${error}`); + }); +}; diff --git a/server/src/internal/balances/thresholdReached/handleThresholdReached.ts b/server/src/internal/balances/trackWebhooks/handleThresholdReached.ts similarity index 68% rename from server/src/internal/balances/thresholdReached/handleThresholdReached.ts rename to server/src/internal/balances/trackWebhooks/handleThresholdReached.ts index 87e628148..2be74591b 100644 --- a/server/src/internal/balances/thresholdReached/handleThresholdReached.ts +++ b/server/src/internal/balances/trackWebhooks/handleThresholdReached.ts @@ -68,56 +68,42 @@ const handleAllowanceUsed = async ({ const prevCusFeature = prevApiCustomer.balances[feature.id]; const newCusFeature = newApiCustomer.balances[feature.id]; - const oldAllowed = apiBalanceToAllowed({ + const { allowed: oldAllowed } = apiBalanceToAllowed({ apiBalance: prevCusFeature, apiSubject: prevApiCustomer, feature, requiredBalance: 1, }); - const newAllowed = apiBalanceToAllowed({ + const { allowed: newAllowed } = apiBalanceToAllowed({ apiBalance: newCusFeature, apiSubject: newApiCustomer, feature, requiredBalance: 1, }); - if (oldAllowed === true && newAllowed === false) { - const customerId = newFullCus.id || newFullCus.internal_id; - - await Promise.all([ - sendSvixEvent({ - org: ctx.org, - env: ctx.env, - eventType: WebhookEventType.CustomerThresholdReached, - data: { - threshold_type: "allowance_used", - customer: cleanApiCustomer({ - ctx, - apiCustomer: newApiCustomer, - legacyData: newLegacyData, - }), - feature: dbToApiFeatureV1({ - ctx, - dbFeature: feature, - targetVersion: ctx.apiVersion, - }), - }, - }), - sendSvixEvent({ - org: ctx.org, - env: ctx.env, - eventType: WebhookEventType.BalancesThresholdReached, - data: { - customer_id: customerId, - feature_id: feature.id, - threshold_type: "allowance_used", - }, - }), - ]); + if (oldAllowed && !newAllowed) { + await sendSvixEvent({ + ctx, + eventType: WebhookEventType.CustomerThresholdReached, + data: { + threshold_type: "allowance_used", + customer: cleanApiCustomer({ + ctx, + apiCustomer: newApiCustomer, + legacyData: newLegacyData, + }), + feature: dbToApiFeatureV1({ + ctx, + dbFeature: feature, + targetVersion: ctx.apiVersion, + }), + }, + }); } }; +/** @deprecated Use checkUsageAlerts / checkLimitReached instead */ export const handleThresholdReached = async ({ ctx, oldFullCus, @@ -153,54 +139,38 @@ export const handleThresholdReached = async ({ fullCus: newFullCus, }); - const oldAllowed = apiBalanceToAllowed({ + const { allowed: oldAllowed } = apiBalanceToAllowed({ apiBalance: prevApiCustomer.balances[feature.id], apiSubject: prevApiCustomer, feature, requiredBalance: 1, }); - const newAllowed = apiBalanceToAllowed({ + const { allowed: newAllowed } = apiBalanceToAllowed({ apiBalance: newApiCustomer.balances[feature.id], apiSubject: newApiCustomer, feature, requiredBalance: 1, }); - if (oldAllowed === true && newAllowed === false) { - const customerId = newFullCus.id || newFullCus.internal_id; - - await Promise.all([ - sendSvixEvent({ - org: ctx.org, - env: ctx.env, - eventType: WebhookEventType.CustomerThresholdReached, - data: { - threshold_type: "limit_reached", - customer: cleanApiCustomer({ - ctx, - apiCustomer: newApiCustomer, - legacyData: newLegacyData, - }), - feature: dbToApiFeatureV1({ - ctx, - dbFeature: feature, - targetVersion: ctx.apiVersion, - }), - }, - }), - sendSvixEvent({ - org: ctx.org, - env: ctx.env, - eventType: WebhookEventType.BalancesThresholdReached, - data: { - customer_id: customerId, - feature_id: feature.id, - threshold_type: "limit_reached", - }, - }), - ]); - + if (oldAllowed && !newAllowed) { + await sendSvixEvent({ + ctx, + eventType: WebhookEventType.CustomerThresholdReached, + data: { + threshold_type: "limit_reached", + customer: cleanApiCustomer({ + ctx, + apiCustomer: newApiCustomer, + legacyData: newLegacyData, + }), + feature: dbToApiFeatureV1({ + ctx, + dbFeature: feature, + targetVersion: ctx.apiVersion, + }), + }, + }); ctx.logger.info( "Sent Svix event for threshold reached (type: limit_reached)", ); @@ -212,10 +182,9 @@ export const handleThresholdReached = async ({ oldFullCus, newFullCus, }); - } catch (error: any) { - ctx.logger.error("Failed to handle threshold reached", { + } catch (error) { + ctx.logger.error(`Failed to handle threshold reached, error: ${error}`, { error, - message: error?.message, }); } }; diff --git a/server/src/internal/balances/utils/deduction/executePostgresDeduction.ts b/server/src/internal/balances/utils/deduction/executePostgresDeduction.ts index 23d29e3f8..d688995d4 100644 --- a/server/src/internal/balances/utils/deduction/executePostgresDeduction.ts +++ b/server/src/internal/balances/utils/deduction/executePostgresDeduction.ts @@ -16,8 +16,7 @@ import type { DeductionUpdate } from "../../utils/types/deductionUpdate.js"; import type { FeatureDeduction } from "../../utils/types/featureDeduction.js"; import type { MutationLogItem } from "../../utils/types/mutationLogItem.js"; import { createAllocatedInvoice } from "../allocatedInvoice/createAllocatedInvoice.js"; -import { checkUsageAlerts } from "../../thresholdReached/checkUsageAlerts.js"; -import { handleThresholdReached } from "../../thresholdReached/handleThresholdReached.js"; +import { fireTrackWebhooks } from "../../trackWebhooks/fireTrackWebhooks.js"; import type { DeductionOptions } from "../types/deductionTypes.js"; import { applyRolloverUpdatesToFullCustomer } from "./applyRolloverUpdatesToFullCustomer.js"; import { @@ -229,26 +228,12 @@ export const executePostgresDeduction = async ({ throw error; } - handleThresholdReached({ + fireTrackWebhooks({ ctx, oldFullCus, newFullCus: fullCustomer, feature: deduction.feature, - }).catch((error) => { - ctx.logger.error( - `[executePostgresDeduction] Failed to handle threshold reached: ${error}`, - ); - }); - - checkUsageAlerts({ - ctx, - oldFullCus, - newFullCus: fullCustomer, - feature: deduction.feature, - }).catch((error) => { - ctx.logger.error( - `[executePostgresDeduction] Failed to check usage alerts: ${error}`, - ); + entityId, }); if (resolvedOptions.triggerAutoTopUp) { diff --git a/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts b/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts index c37f6fc10..9279ee35f 100644 --- a/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts +++ b/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts @@ -10,8 +10,7 @@ import { handlePaidAllocatedCusEnt } from "@/internal/balances/utils/paidAllocat import { rollbackDeduction } from "@/internal/balances/utils/paidAllocatedFeature/rollbackDeduction.js"; import { buildFullCustomerCacheKey } from "@/internal/customers/cusUtils/fullCustomerCacheUtils/fullCustomerCacheConfig.js"; import { tryRedisWrite } from "@/utils/cacheUtils/cacheUtils.js"; -import { checkUsageAlerts } from "../../thresholdReached/checkUsageAlerts.js"; -import { handleThresholdReached } from "../../thresholdReached/handleThresholdReached.js"; +import { fireTrackWebhooks } from "../../trackWebhooks/fireTrackWebhooks.js"; import type { DeductionOptions } from "../types/deductionTypes.js"; import type { DeductionUpdate } from "../types/deductionUpdate.js"; import type { FeatureDeduction } from "../types/featureDeduction.js"; @@ -236,26 +235,12 @@ export const executeRedisDeduction = async ({ throw error; } - handleThresholdReached({ + fireTrackWebhooks({ ctx, oldFullCus, newFullCus: fullCustomer, feature: deduction.feature, - }).catch((error) => { - ctx.logger.error( - `[executeRedisDeduction] Failed to handle threshold reached: ${error}`, - ); - }); - - checkUsageAlerts({ - ctx, - oldFullCus, - newFullCus: fullCustomer, - feature: deduction.feature, - }).catch((error) => { - ctx.logger.error( - `[executeRedisDeduction] Failed to check usage alerts: ${error}`, - ); + entityId, }); if (options.triggerAutoTopUp) { diff --git a/server/src/internal/billing/v2/workflows/sendProductsUpdated/sendProductsUpdated.ts b/server/src/internal/billing/v2/workflows/sendProductsUpdated/sendProductsUpdated.ts index 6f4d78c01..12a743974 100644 --- a/server/src/internal/billing/v2/workflows/sendProductsUpdated/sendProductsUpdated.ts +++ b/server/src/internal/billing/v2/workflows/sendProductsUpdated/sendProductsUpdated.ts @@ -37,7 +37,7 @@ export const sendProductsUpdated = async ({ ctx: AutumnContext; payload: SendProductsUpdatedPayload; }) => { - const { db, org, env, features, logger } = ctx; + const { features, logger } = ctx; const { customerProductId, scenario, customerId } = payload; // Fetch FullCustomer @@ -139,8 +139,7 @@ export const sendProductsUpdated = async ({ ); await sendSvixEvent({ - org, - env, + ctx, eventType: "customer.products.updated", data: { scenario, diff --git a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/updateCachedCustomerData.ts b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/updateCachedCustomerData.ts index 3047724e1..20f620d09 100644 --- a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/updateCachedCustomerData.ts +++ b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/updateCachedCustomerData.ts @@ -22,6 +22,7 @@ type CustomerDataUpdates = Pick< | "processors" | "auto_topups" | "spend_limits" + | "usage_alerts" >; /** diff --git a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/updateEntityInCache.ts b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/updateEntityInCache.ts index 635d45c9e..88be31a22 100644 --- a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/updateEntityInCache.ts +++ b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/updateEntityInCache.ts @@ -36,7 +36,7 @@ export const updateEntityInCache = async ({ ctx: AutumnContext; customerId: string; idOrInternalId: string; - updates: Partial>; + updates: Partial>; }): Promise => { try { if (Object.keys(updates).length === 0) { diff --git a/server/src/internal/customers/cusUtils/initCustomer.ts b/server/src/internal/customers/cusUtils/initCustomer.ts index ae809f37e..8ab40b74b 100644 --- a/server/src/internal/customers/cusUtils/initCustomer.ts +++ b/server/src/internal/customers/cusUtils/initCustomer.ts @@ -36,6 +36,7 @@ const initCustomer = ({ send_email_receipts: customerData?.send_email_receipts ?? false, auto_topups: customerData?.billing_controls?.auto_topups, spend_limits: customerData?.billing_controls?.spend_limits, + usage_alerts: customerData?.billing_controls?.usage_alerts, }; }; diff --git a/server/src/internal/entities/actions/batchCreateEntities.ts b/server/src/internal/entities/actions/batchCreateEntities.ts index 36564e9ce..f0ee6fae6 100644 --- a/server/src/internal/entities/actions/batchCreateEntities.ts +++ b/server/src/internal/entities/actions/batchCreateEntities.ts @@ -75,6 +75,7 @@ export const batchCreateEntities = async ({ name: inputEntities[0].name, ...(inputEntities[0].billing_controls && { spend_limits: inputEntities[0].billing_controls.spend_limits, + usage_alerts: inputEntities[0].billing_controls.usage_alerts, }), }, }); diff --git a/server/src/internal/entities/actions/updateEntity.ts b/server/src/internal/entities/actions/updateEntity.ts index 7563e8c93..34606cd3c 100644 --- a/server/src/internal/entities/actions/updateEntity.ts +++ b/server/src/internal/entities/actions/updateEntity.ts @@ -44,6 +44,7 @@ export const updateEntity = async ({ entity, updates: { spend_limits: billing_controls?.spend_limits, + usage_alerts: billing_controls?.usage_alerts, }, }); diff --git a/server/src/internal/entities/actions/updateEntityDbAndCache.ts b/server/src/internal/entities/actions/updateEntityDbAndCache.ts index 6bc0e05a5..e5483f7a9 100644 --- a/server/src/internal/entities/actions/updateEntityDbAndCache.ts +++ b/server/src/internal/entities/actions/updateEntityDbAndCache.ts @@ -12,11 +12,11 @@ export const updateEntityDbAndCache = async ({ ctx: AutumnContext; customerId: string; entity: Entity; - updates: Partial>; + updates: Partial>; }) => { const filteredUpdates = Object.fromEntries( Object.entries(updates).filter(([, value]) => value !== undefined), - ) as Partial>; + ) as Partial>; if (Object.keys(filteredUpdates).length === 0) { return entity; diff --git a/server/src/internal/entities/entityUtils/apiEntityUtils/getApiEntityBase.ts b/server/src/internal/entities/entityUtils/apiEntityUtils/getApiEntityBase.ts index 37a93b9c1..555e6a0de 100644 --- a/server/src/internal/entities/entityUtils/apiEntityUtils/getApiEntityBase.ts +++ b/server/src/internal/entities/entityUtils/apiEntityUtils/getApiEntityBase.ts @@ -72,7 +72,10 @@ export const getApiEntityBase = async ({ purchases: apiPurchases, balances: apiBalances, flags: apiFlags, - billing_controls: { spend_limits: entity.spend_limits ?? undefined }, + billing_controls: { + spend_limits: entity.spend_limits ?? undefined, + usage_alerts: entity.usage_alerts ?? undefined, + }, } satisfies ApiEntityV2); return { diff --git a/server/src/internal/entities/entityUtils/entityUtils.ts b/server/src/internal/entities/entityUtils/entityUtils.ts index f18ebb7af..eda4bbde6 100644 --- a/server/src/internal/entities/entityUtils/entityUtils.ts +++ b/server/src/internal/entities/entityUtils/entityUtils.ts @@ -33,6 +33,7 @@ export const constructEntity = ({ deleted, created_at: Date.now(), spend_limits: inputEntity.billing_controls?.spend_limits, + usage_alerts: inputEntity.billing_controls?.usage_alerts, }; return entity; diff --git a/server/src/internal/entities/handlers/handleListEntities.ts b/server/src/internal/entities/handlers/handleListEntities.ts index f3d0018fd..cfd6895b5 100644 --- a/server/src/internal/entities/handlers/handleListEntities.ts +++ b/server/src/internal/entities/handlers/handleListEntities.ts @@ -13,10 +13,15 @@ export const handleListEntities = createRoute({ }); return c.json({ - list: fullCus.entities.map(({ spend_limits, ...entity }) => ({ - ...entity, - billing_controls: { spend_limits: spend_limits ?? undefined }, - })), + list: fullCus.entities.map( + ({ spend_limits, usage_alerts, ...entity }) => ({ + ...entity, + billing_controls: { + spend_limits: spend_limits ?? undefined, + usage_alerts: usage_alerts ?? undefined, + }, + }), + ), }); }, }); diff --git a/server/tests/integration/balances/track/limit-reached/limit-reached-customer.test.ts b/server/tests/integration/balances/track/limit-reached/limit-reached-customer.test.ts new file mode 100644 index 000000000..718cf2252 --- /dev/null +++ b/server/tests/integration/balances/track/limit-reached/limit-reached-customer.test.ts @@ -0,0 +1,353 @@ +/** + * Integration tests for the `balances.limit_reached` webhook at the customer level. + * + * Verifies that the webhook fires when a customer's balance transitions from + * allowed → not allowed, with the correct `limit_type` for each scenario: + * - included: free allowance exhausted + * - max_purchase: consumable overage cap reached + * - spend_limit: customer-level spend limit reached + * + * Also verifies no-fire / no-refire behavior. + */ + +import { afterAll, beforeAll, expect, test } from "bun:test"; +import { + getPlayHistory, + getTestSvixAppId, + parseEventBody, + setupWebhookTest, + type WebhookTestSetup, + waitForWebhook, +} from "@tests/integration/utils/svixWebhookTestUtils.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import ctx from "@tests/utils/testInitUtils/createTestContext.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { setCustomerSpendLimit } from "../../utils/spend-limit-utils/customerSpendLimitUtils.js"; + +type BalancesLimitReachedPayload = { + type: string; + data: { + customer_id: string; + feature_id: string; + limit_type: string; + entity_id?: string; + }; +}; + +// ═══════════════════════════════════════════════════════════════════════════════ +// SVIX PLAY SETUP +// ═══════════════════════════════════════════════════════════════════════════════ + +let webhook: WebhookTestSetup; +let playToken: string; + +beforeAll(async () => { + const appId = getTestSvixAppId({ svixConfig: ctx.org.svix_config }); + webhook = await setupWebhookTest({ + appId, + filterTypes: ["balances.limit_reached"], + }); + playToken = webhook.playToken; +}); + +afterAll(async () => { + await webhook?.cleanup(); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 1: included — free allowance exhausted triggers webhook +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("limit-reached-cus1: included allowance exhausted fires webhook")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 100 }); + const freeProd = products.base({ + id: "lr-included-1", + items: [messagesItem], + }); + + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "lr-included-cus-1", + setup: [s.customer({ testClock: false }), s.products({ list: [freeProd] })], + actions: [s.attach({ productId: freeProd.id })], + }); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 100, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId && + payload.data?.limit_type === "included", + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + expect(result?.payload.type).toBe("balances.limit_reached"); + + const { data } = result!.payload; + expect(data.customer_id).toBe(customerId); + expect(data.feature_id).toBe(TestFeature.Messages); + expect(data.limit_type).toBe("included"); + expect(data.entity_id).toBeUndefined(); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 2: max_purchase — consumable overage cap reached triggers webhook +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("limit-reached-cus2: max_purchase cap reached fires webhook")}`, async () => { + const consumableMsg = items.consumableMessages({ + includedUsage: 50, + maxPurchase: 50, + }); + const proProd = products.pro({ + id: "lr-max-purchase-1", + items: [consumableMsg], + }); + + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "lr-maxpurchase-cus-1", + setup: [ + s.customer({ testClock: false, paymentMethod: "success" }), + s.products({ list: [proProd] }), + ], + actions: [s.attach({ productId: proProd.id })], + }); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 100, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId && + payload.data?.limit_type === "max_purchase", + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + const { data } = result!.payload; + expect(data.customer_id).toBe(customerId); + expect(data.feature_id).toBe(TestFeature.Messages); + expect(data.limit_type).toBe("max_purchase"); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 3: spend_limit — customer-level spend limit reached triggers webhook +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("limit-reached-cus3: spend_limit reached fires webhook")}`, async () => { + const consumableMsg = items.consumableMessages({ + includedUsage: 50, + price: 1, + }); + const proProd = products.pro({ + id: "lr-spend-limit-1", + items: [consumableMsg], + }); + + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "lr-spendlimit-cus-1", + setup: [ + s.customer({ testClock: false, paymentMethod: "success" }), + s.products({ list: [proProd] }), + ], + actions: [s.attach({ productId: proProd.id })], + }); + + await setCustomerSpendLimit({ + autumn: autumnV2_1, + customerId, + featureId: TestFeature.Messages, + overageLimit: 10, + }); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 60, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId && + payload.data?.limit_type === "spend_limit", + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + const { data } = result!.payload; + expect(data.customer_id).toBe(customerId); + expect(data.feature_id).toBe(TestFeature.Messages); + expect(data.limit_type).toBe("spend_limit"); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 4: no-fire — usage below limit does not trigger webhook +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("limit-reached-cus4: usage below limit does not fire webhook")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 1000 }); + const freeProd = products.base({ + id: "lr-no-fire-1", + items: [messagesItem], + }); + + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "lr-nofire-cus-1", + setup: [s.customer({ testClock: false }), s.products({ list: [freeProd] })], + actions: [s.attach({ productId: freeProd.id })], + }); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 500, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId, + timeoutMs: 8000, + }); + + expect(result).toBeNull(); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 5: no-refire — second track after limit already reached does not refire +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("limit-reached-cus5: does not refire after limit already reached")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 100 }); + const freeProd = products.base({ + id: "lr-no-refire-1", + items: [messagesItem], + }); + + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "lr-norefire-cus-1", + setup: [s.customer({ testClock: false }), s.products({ list: [freeProd] })], + actions: [s.attach({ productId: freeProd.id })], + }); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 100, + }); + + const firstResult = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId, + timeoutMs: 15000, + }); + + expect(firstResult).not.toBeNull(); + + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 50, + }); + + await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId, + timeoutMs: 8000, + }); + + let matchCount = 0; + const history = await getPlayHistory({ token: playToken }); + for (const event of history.data) { + try { + const payload = parseEventBody(event); + if ( + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId + ) { + matchCount++; + } + } catch { + // Skip unparseable events + } + } + + expect(matchCount).toBe(1); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 6: precision — fractional usage doesn't false-trigger limit reached +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("limit-reached-cus6: precision — 0.001 remaining does not trigger, 0 remaining does")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 1 }); + const freeProd = products.base({ + id: "lr-precision-1", + items: [messagesItem], + }); + + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "lr-precision-cus-1", + setup: [s.customer({ testClock: false }), s.products({ list: [freeProd] })], + actions: [s.attach({ productId: freeProd.id })], + }); + + // Track 0.999 — leaves 0.001 remaining, should NOT trigger + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 0.999, + }); + + const noFireResult = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId, + timeoutMs: 8000, + }); + + expect(noFireResult).toBeNull(); + + // Track 0.001 more — now exactly 0 remaining, SHOULD trigger + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 0.001, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId, + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + const { data } = result!.payload; + expect(data.customer_id).toBe(customerId); + expect(data.feature_id).toBe(TestFeature.Messages); + expect(data.limit_type).toBe("included"); +}); diff --git a/server/tests/integration/balances/track/limit-reached/limit-reached-entities.test.ts b/server/tests/integration/balances/track/limit-reached/limit-reached-entities.test.ts new file mode 100644 index 000000000..b700f5142 --- /dev/null +++ b/server/tests/integration/balances/track/limit-reached/limit-reached-entities.test.ts @@ -0,0 +1,298 @@ +/** + * Integration tests for the `balances.limit_reached` webhook at the entity level. + * + * `checkLimitReached` fires exactly once: entity-level when `entityId` is + * provided, customer-level otherwise. These tests verify that entity-scoped + * tracking only produces an entity-scoped webhook (no extra customer-level fire). + */ + +import { afterAll, beforeAll, expect, test } from "bun:test"; +import type { EntityBillingControls } from "@autumn/shared"; +import { + getPlayHistory, + getTestSvixAppId, + parseEventBody, + setupWebhookTest, + type WebhookTestSetup, + waitForWebhook, +} from "@tests/integration/utils/svixWebhookTestUtils.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import ctx from "@tests/utils/testInitUtils/createTestContext.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; + +type BalancesLimitReachedPayload = { + type: string; + data: { + customer_id: string; + feature_id: string; + limit_type: string; + entity_id?: string; + }; +}; + +// ═══════════════════════════════════════════════════════════════════════════════ +// SVIX PLAY SETUP +// ═══════════════════════════════════════════════════════════════════════════════ + +let webhook: WebhookTestSetup; +let playToken: string; + +beforeAll(async () => { + const appId = getTestSvixAppId({ svixConfig: ctx.org.svix_config }); + webhook = await setupWebhookTest({ + appId, + filterTypes: ["balances.limit_reached"], + }); + playToken = webhook.playToken; +}); + +afterAll(async () => { + await webhook?.cleanup(); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 1: entity included — per-entity allowance exhausted +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("limit-reached-ent1: per-entity included allowance exhausted fires webhook")}`, async () => { + const perEntityMessages = items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }); + const prod = products.base({ + id: "lr-ent-included-1", + items: [perEntityMessages], + }); + + const { customerId, autumnV2_1, entities } = await initScenario({ + customerId: "lr-ent-included-cus-1", + setup: [ + s.customer({ testClock: false }), + s.products({ list: [prod] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.attach({ productId: prod.id })], + }); + + await autumnV2_1.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 100, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId && + payload.data?.entity_id === entities[0].id && + payload.data?.limit_type === "included", + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + const { data } = result!.payload; + expect(data.customer_id).toBe(customerId); + expect(data.entity_id).toBe(entities[0].id); + expect(data.feature_id).toBe(TestFeature.Messages); + expect(data.limit_type).toBe("included"); + + // Single-fire: no customer-level webhook (without entity_id) should exist + await timeout(3000); + let customerLevelFired = false; + const history = await getPlayHistory({ token: playToken }); + for (const event of history.data) { + try { + const payload = parseEventBody(event); + if ( + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId && + !payload.data?.entity_id + ) { + customerLevelFired = true; + } + } catch { + // Skip + } + } + expect(customerLevelFired).toBe(false); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 2: entity max_purchase — per-entity consumable cap reached +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("limit-reached-ent2: per-entity max_purchase cap reached fires webhook")}`, async () => { + const consumableMsg = items.consumableMessages({ + includedUsage: 50, + maxPurchase: 50, + entityFeatureId: TestFeature.Users, + }); + const proProd = products.pro({ + id: "lr-ent-maxpurchase-1", + items: [consumableMsg], + }); + + const { customerId, autumnV2_1, entities } = await initScenario({ + customerId: "lr-ent-maxpurchase-cus-1", + setup: [ + s.customer({ testClock: false, paymentMethod: "success" }), + s.products({ list: [proProd] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.attach({ productId: proProd.id })], + }); + + await autumnV2_1.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 100, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId && + payload.data?.entity_id === entities[0].id && + payload.data?.limit_type === "max_purchase", + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + const { data } = result!.payload; + expect(data.entity_id).toBe(entities[0].id); + expect(data.limit_type).toBe("max_purchase"); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 3: entity spend_limit — per-entity spend limit reached +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("limit-reached-ent3: per-entity spend_limit reached fires webhook")}`, async () => { + const consumableMsg = items.consumableMessages({ + includedUsage: 50, + price: 1, + entityFeatureId: TestFeature.Users, + }); + const proProd = products.pro({ + id: "lr-ent-spendlimit-1", + items: [consumableMsg], + }); + + const { customerId, autumnV2_1, entities } = await initScenario({ + customerId: "lr-ent-spendlimit-cus-1", + setup: [ + s.customer({ testClock: false, paymentMethod: "success" }), + s.products({ list: [proProd] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.attach({ productId: proProd.id })], + }); + + await autumnV2_1.entities.update(customerId, entities[0].id, { + billing_controls: { + spend_limits: [ + { + feature_id: TestFeature.Messages, + enabled: true, + overage_limit: 10, + }, + ], + } as EntityBillingControls, + }); + + await autumnV2_1.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 60, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId && + payload.data?.entity_id === entities[0].id && + payload.data?.limit_type === "spend_limit", + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + const { data } = result!.payload; + expect(data.entity_id).toBe(entities[0].id); + expect(data.limit_type).toBe("spend_limit"); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 4: two entities — only the entity that hits limit fires webhook +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("limit-reached-ent4: two entities, only entity hitting limit fires webhook")}`, async () => { + const perEntityMessages = items.monthlyMessages({ + includedUsage: 100, + entityFeatureId: TestFeature.Users, + }); + const prod = products.base({ + id: "lr-ent-twoents-1", + items: [perEntityMessages], + }); + + const { customerId, autumnV2_1, entities } = await initScenario({ + customerId: "lr-ent-twoents-cus-1", + setup: [ + s.customer({ testClock: false }), + s.products({ list: [prod] }), + s.entities({ count: 2, featureId: TestFeature.Users }), + ], + actions: [s.attach({ productId: prod.id })], + }); + + await autumnV2_1.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 100, + }); + + const entity1Result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId && + payload.data?.entity_id === entities[0].id, + timeoutMs: 15000, + }); + + expect(entity1Result).not.toBeNull(); + expect(entity1Result!.payload.data.entity_id).toBe(entities[0].id); + + await timeout(5000); + + let entity2Fired = false; + let customerLevelFired = false; + const history = await getPlayHistory({ token: playToken }); + for (const event of history.data) { + try { + const payload = parseEventBody(event); + if ( + payload.type === "balances.limit_reached" && + payload.data?.customer_id === customerId + ) { + if (payload.data?.entity_id === entities[1].id) entity2Fired = true; + if (!payload.data?.entity_id) customerLevelFired = true; + } + } catch { + // Skip + } + } + expect(entity2Fired).toBe(false); + expect(customerLevelFired).toBe(false); +}); diff --git a/server/tests/integration/balances/track/usage-alerts/usage-alert-basic.test.ts b/server/tests/integration/balances/track/usage-alerts/usage-alert-basic.test.ts index 72a72f76f..98fc9adf1 100644 --- a/server/tests/integration/balances/track/usage-alerts/usage-alert-basic.test.ts +++ b/server/tests/integration/balances/track/usage-alerts/usage-alert-basic.test.ts @@ -1,7 +1,7 @@ /** * Integration tests for usage alert webhooks. * - * Verifies that `balances.threshold_reached` webhooks fire correctly when + * Verifies that `balances.usage_alert_triggered` webhooks fire correctly when * customer usage crosses configured thresholds (both absolute and percentage). * * Uses Svix Play to receive and verify webhooks. @@ -9,12 +9,13 @@ import { afterAll, beforeAll, expect, test } from "bun:test"; import { - generatePlayToken, getPlayHistory, - getPlayWebhookUrl, + getTestSvixAppId, parseEventBody, + setupWebhookTest, + type WebhookTestSetup, waitForWebhook, -} from "@tests/integration/billing/autumn-webhooks/utils/svixPlayClient.js"; +} from "@tests/integration/utils/svixWebhookTestUtils.js"; import { TestFeature } from "@tests/setup/v2Features.js"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; @@ -23,18 +24,13 @@ import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; import chalk from "chalk"; import { setCustomerUsageAlerts } from "../../utils/usage-alert-utils/customerUsageAlertUtils.js"; -import { - createUsageAlertTestEndpoint, - deleteUsageAlertTestEndpoint, -} from "../../utils/usage-alert-utils/svixUsageAlertEndpoint.js"; -type BalancesThresholdReachedPayload = { +type BalancesUsageAlertTriggeredPayload = { type: string; data: { customer_id: string; feature_id: string; - threshold_type: string; - usage_alert?: { + usage_alert: { name?: string; threshold: number; threshold_type: string; @@ -46,36 +42,20 @@ type BalancesThresholdReachedPayload = { // SVIX PLAY SETUP // ═══════════════════════════════════════════════════════════════════════════════ +let webhook: WebhookTestSetup; let playToken: string; -let endpointId: string; beforeAll(async () => { - playToken = await generatePlayToken(); - console.log(`Generated Svix Play token: ${playToken}`); - - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (!svixAppId) { - throw new Error( - "Test org does not have svix_config.sandbox_app_id configured. " + - "Cannot run webhook integration tests without Svix app.", - ); - } - - const playUrl = getPlayWebhookUrl(playToken); - console.log(`Creating Svix endpoint: ${playUrl}`); - endpointId = await createUsageAlertTestEndpoint({ - appId: svixAppId, - playUrl, + const appId = getTestSvixAppId({ svixConfig: ctx.org.svix_config }); + webhook = await setupWebhookTest({ + appId, + filterTypes: ["balances.usage_alert_triggered"], }); - console.log(`Created Svix endpoint: ${endpointId}`); + playToken = webhook.playToken; }); afterAll(async () => { - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (svixAppId && endpointId) { - await deleteUsageAlertTestEndpoint({ appId: svixAppId, endpointId }); - console.log(`Deleted Svix endpoint: ${endpointId}`); - } + await webhook?.cleanup(); }); // ═══════════════════════════════════════════════════════════════════════════════ @@ -102,7 +82,7 @@ test(`${chalk.yellowBright("usage-alert1: usage threshold crossing triggers webh { feature_id: TestFeature.Messages, threshold: 800, - threshold_type: "usage_threshold", + threshold_type: "usage", enabled: true, }, ], @@ -115,26 +95,23 @@ test(`${chalk.yellowBright("usage-alert1: usage threshold crossing triggers webh value: 850, }); - const result = await waitForWebhook({ + const result = await waitForWebhook({ token: playToken, predicate: (payload) => - payload.type === "balances.threshold_reached" && + payload.type === "balances.usage_alert_triggered" && payload.data?.customer_id === customerId && - payload.data?.threshold_type === "usage_alert" && payload.data?.usage_alert?.threshold === 800, timeoutMs: 15000, }); expect(result).not.toBeNull(); - expect(result?.payload.type).toBe("balances.threshold_reached"); + expect(result?.payload.type).toBe("balances.usage_alert_triggered"); const { data } = result!.payload; expect(data.customer_id).toBe(customerId); expect(data.feature_id).toBe(TestFeature.Messages); - expect(data.threshold_type).toBe("usage_alert"); - expect(data.usage_alert).toBeDefined(); - expect(data.usage_alert!.threshold).toBe(800); - expect(data.usage_alert!.threshold_type).toBe("usage_threshold"); + expect(data.usage_alert.threshold).toBe(800); + expect(data.usage_alert.threshold_type).toBe("usage"); }); // ═══════════════════════════════════════════════════════════════════════════════ @@ -161,7 +138,7 @@ test(`${chalk.yellowBright("usage-alert2: percentage threshold crossing triggers { feature_id: TestFeature.Messages, threshold: 90, - threshold_type: "usage_percentage_threshold", + threshold_type: "usage_percentage", enabled: true, }, ], @@ -174,26 +151,23 @@ test(`${chalk.yellowBright("usage-alert2: percentage threshold crossing triggers value: 950, }); - const result = await waitForWebhook({ + const result = await waitForWebhook({ token: playToken, predicate: (payload) => - payload.type === "balances.threshold_reached" && + payload.type === "balances.usage_alert_triggered" && payload.data?.customer_id === customerId && - payload.data?.threshold_type === "usage_alert" && payload.data?.usage_alert?.threshold === 90, timeoutMs: 15000, }); expect(result).not.toBeNull(); - expect(result?.payload.type).toBe("balances.threshold_reached"); + expect(result?.payload.type).toBe("balances.usage_alert_triggered"); const { data } = result!.payload; expect(data.customer_id).toBe(customerId); expect(data.feature_id).toBe(TestFeature.Messages); - expect(data.threshold_type).toBe("usage_alert"); - expect(data.usage_alert).toBeDefined(); - expect(data.usage_alert!.threshold).toBe(90); - expect(data.usage_alert!.threshold_type).toBe("usage_percentage_threshold"); + expect(data.usage_alert.threshold).toBe(90); + expect(data.usage_alert.threshold_type).toBe("usage_percentage"); }); // ═══════════════════════════════════════════════════════════════════════════════ @@ -220,7 +194,7 @@ test(`${chalk.yellowBright("usage-alert3: alert does not re-fire after already c { feature_id: TestFeature.Messages, threshold: 500, - threshold_type: "usage_threshold", + threshold_type: "usage", enabled: true, }, ], @@ -233,10 +207,10 @@ test(`${chalk.yellowBright("usage-alert3: alert does not re-fire after already c value: 600, }); - const firstResult = await waitForWebhook({ + const firstResult = await waitForWebhook({ token: playToken, predicate: (payload) => - payload.type === "balances.threshold_reached" && + payload.type === "balances.usage_alert_triggered" && payload.data?.customer_id === customerId && payload.data?.usage_alert?.threshold === 500, timeoutMs: 15000, @@ -252,10 +226,10 @@ test(`${chalk.yellowBright("usage-alert3: alert does not re-fire after already c }); // Wait briefly, then assert no second webhook arrived - await waitForWebhook({ + await waitForWebhook({ token: playToken, predicate: (payload) => - payload.type === "balances.threshold_reached" && + payload.type === "balances.usage_alert_triggered" && payload.data?.customer_id === customerId && payload.data?.usage_alert?.threshold === 500, timeoutMs: 8000, @@ -267,9 +241,9 @@ test(`${chalk.yellowBright("usage-alert3: alert does not re-fire after already c const history = await getPlayHistory({ token: playToken }); for (const event of history.data) { try { - const payload = parseEventBody(event); + const payload = parseEventBody(event); if ( - payload.type === "balances.threshold_reached" && + payload.type === "balances.usage_alert_triggered" && payload.data?.customer_id === customerId && payload.data?.usage_alert?.threshold === 500 ) { @@ -308,7 +282,7 @@ test(`${chalk.yellowBright("usage-alert4: disabled alert does not fire")}`, asyn { feature_id: TestFeature.Messages, threshold: 500, - threshold_type: "usage_threshold", + threshold_type: "usage", enabled: false, }, ], @@ -322,10 +296,10 @@ test(`${chalk.yellowBright("usage-alert4: disabled alert does not fire")}`, asyn }); // Wait and verify no webhook - const result = await waitForWebhook({ + const result = await waitForWebhook({ token: playToken, predicate: (payload) => - payload.type === "balances.threshold_reached" && + payload.type === "balances.usage_alert_triggered" && payload.data?.customer_id === customerId && payload.data?.usage_alert?.threshold === 500, timeoutMs: 8000, @@ -358,13 +332,13 @@ test(`${chalk.yellowBright("usage-alert5: multiple alerts fire independently")}` { feature_id: TestFeature.Messages, threshold: 500, - threshold_type: "usage_threshold", + threshold_type: "usage", enabled: true, }, { feature_id: TestFeature.Messages, threshold: 800, - threshold_type: "usage_threshold", + threshold_type: "usage", enabled: true, }, ], @@ -377,17 +351,17 @@ test(`${chalk.yellowBright("usage-alert5: multiple alerts fire independently")}` value: 600, }); - const firstResult = await waitForWebhook({ + const firstResult = await waitForWebhook({ token: playToken, predicate: (payload) => - payload.type === "balances.threshold_reached" && + payload.type === "balances.usage_alert_triggered" && payload.data?.customer_id === customerId && payload.data?.usage_alert?.threshold === 500, timeoutMs: 15000, }); expect(firstResult).not.toBeNull(); - expect(firstResult!.payload.data.usage_alert!.threshold).toBe(500); + expect(firstResult!.payload.data.usage_alert.threshold).toBe(500); // Verify 800 threshold has NOT fired yet await timeout(3000); @@ -396,9 +370,9 @@ test(`${chalk.yellowBright("usage-alert5: multiple alerts fire independently")}` const historyMid = await getPlayHistory({ token: playToken }); for (const event of historyMid.data) { try { - const payload = parseEventBody(event); + const payload = parseEventBody(event); if ( - payload.type === "balances.threshold_reached" && + payload.type === "balances.usage_alert_triggered" && payload.data?.customer_id === customerId && payload.data?.usage_alert?.threshold === 800 ) { @@ -417,15 +391,15 @@ test(`${chalk.yellowBright("usage-alert5: multiple alerts fire independently")}` value: 300, }); - const secondResult = await waitForWebhook({ + const secondResult = await waitForWebhook({ token: playToken, predicate: (payload) => - payload.type === "balances.threshold_reached" && + payload.type === "balances.usage_alert_triggered" && payload.data?.customer_id === customerId && payload.data?.usage_alert?.threshold === 800, timeoutMs: 15000, }); expect(secondResult).not.toBeNull(); - expect(secondResult!.payload.data.usage_alert!.threshold).toBe(800); + expect(secondResult!.payload.data.usage_alert.threshold).toBe(800); }); diff --git a/server/tests/integration/balances/track/usage-alerts/usage-alert-entities.test.ts b/server/tests/integration/balances/track/usage-alerts/usage-alert-entities.test.ts new file mode 100644 index 000000000..0ca6e1fa2 --- /dev/null +++ b/server/tests/integration/balances/track/usage-alerts/usage-alert-entities.test.ts @@ -0,0 +1,464 @@ +/** + * Integration tests for entity-scoped usage alert webhooks. + * + * Verifies that `balances.usage_alert_triggered` webhooks fire correctly + * when entity-level usage alerts are configured, including: + * - Per-entity items (entityFeatureId scoping) + * - Entity-level alerts on individual entities + * - Multiple entities with different alert thresholds + * - Customer-level alerts still fire alongside entity-level alerts + */ + +import { afterAll, beforeAll, expect, test } from "bun:test"; +import type { EntityBillingControls } from "@autumn/shared"; +import { + getPlayHistory, + getTestSvixAppId, + parseEventBody, + setupWebhookTest, + type WebhookTestSetup, + waitForWebhook, +} from "@tests/integration/utils/svixWebhookTestUtils.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import ctx from "@tests/utils/testInitUtils/createTestContext.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; + +type BalancesUsageAlertTriggeredPayload = { + type: string; + data: { + customer_id: string; + feature_id: string; + entity_id?: string; + usage_alert: { + name?: string; + threshold: number; + threshold_type: string; + }; + }; +}; + +// ═══════════════════════════════════════════════════════════════════════════════ +// SVIX PLAY SETUP +// ═══════════════════════════════════════════════════════════════════════════════ + +let webhook: WebhookTestSetup; +let playToken: string; + +beforeAll(async () => { + const appId = getTestSvixAppId({ svixConfig: ctx.org.svix_config }); + webhook = await setupWebhookTest({ + appId, + filterTypes: ["balances.usage_alert_triggered"], + }); + playToken = webhook.playToken; +}); + +afterAll(async () => { + await webhook?.cleanup(); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 1: Per-entity item — entity-level usage alert triggers with entity_id +// ═══════════════════════════════════════════════════════════════════════════════ + +test(`${chalk.yellowBright("entity-alert1: per-entity item triggers entity-scoped alert with entity_id")}`, async () => { + const perEntityMessages = items.monthlyMessages({ + includedUsage: 500, + entityFeatureId: TestFeature.Users, + }); + const prod = products.base({ + id: "ea-per-entity-1", + items: [perEntityMessages], + }); + + const { customerId, autumnV2_1, entities } = await initScenario({ + customerId: "entity-alert-per-entity-1", + setup: [ + s.customer({ testClock: false }), + s.products({ list: [prod] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.attach({ productId: prod.id })], + }); + + // Set entity-level usage alert + await autumnV2_1.entities.update(customerId, entities[0].id, { + billing_controls: { + usage_alerts: [ + { + feature_id: TestFeature.Messages, + threshold: 400, + threshold_type: "usage", + enabled: true, + }, + ], + } as EntityBillingControls, + }); + + // Track 450 usage on entity — crosses threshold of 400 + await autumnV2_1.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 450, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.usage_alert_triggered" && + payload.data?.customer_id === customerId && + payload.data?.entity_id === entities[0].id && + payload.data?.usage_alert?.threshold === 400, + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + expect(result?.payload.type).toBe("balances.usage_alert_triggered"); + + const { data } = result!.payload; + expect(data.customer_id).toBe(customerId); + expect(data.feature_id).toBe(TestFeature.Messages); + expect(data.entity_id).toBe(entities[0].id); + expect(data.usage_alert.threshold).toBe(400); + expect(data.usage_alert.threshold_type).toBe("usage"); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 2: Two entities with different thresholds — only the crossed one fires +// ═══════════════════════════════════════════════════════════════════════════════ + +test(`${chalk.yellowBright("entity-alert2: two entities, only entity crossing threshold fires alert")}`, async () => { + const perEntityMessages = items.monthlyMessages({ + includedUsage: 1000, + entityFeatureId: TestFeature.Users, + }); + const prod = products.base({ + id: "ea-two-ents-1", + items: [perEntityMessages], + }); + + const { customerId, autumnV2_1, entities } = await initScenario({ + customerId: "entity-alert-two-ents-1", + setup: [ + s.customer({ testClock: false }), + s.products({ list: [prod] }), + s.entities({ count: 2, featureId: TestFeature.Users }), + ], + actions: [s.attach({ productId: prod.id })], + }); + + // Entity 1: alert at 300 + await autumnV2_1.entities.update(customerId, entities[0].id, { + billing_controls: { + usage_alerts: [ + { + feature_id: TestFeature.Messages, + threshold: 300, + threshold_type: "usage", + enabled: true, + }, + ], + } as EntityBillingControls, + }); + + // Entity 2: alert at 700 + await autumnV2_1.entities.update(customerId, entities[1].id, { + billing_controls: { + usage_alerts: [ + { + feature_id: TestFeature.Messages, + threshold: 700, + threshold_type: "usage", + enabled: true, + }, + ], + } as EntityBillingControls, + }); + + // Track 400 on entity 1 — crosses its 300 threshold + await autumnV2_1.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 400, + }); + + const entity1Result = + await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.usage_alert_triggered" && + payload.data?.customer_id === customerId && + payload.data?.entity_id === entities[0].id && + payload.data?.usage_alert?.threshold === 300, + timeoutMs: 15000, + }); + + expect(entity1Result).not.toBeNull(); + expect(entity1Result!.payload.data.entity_id).toBe(entities[0].id); + + // Wait and verify entity 2's alert (700) has NOT fired + await timeout(5000); + + let entity2Fired = false; + const history = await getPlayHistory({ token: playToken }); + for (const event of history.data) { + try { + const payload = parseEventBody(event); + if ( + payload.type === "balances.usage_alert_triggered" && + payload.data?.customer_id === customerId && + payload.data?.entity_id === entities[1].id && + payload.data?.usage_alert?.threshold === 700 + ) { + entity2Fired = true; + } + } catch { + // Skip + } + } + expect(entity2Fired).toBe(false); + + // Now track 800 on entity 2 — crosses its 700 threshold + await autumnV2_1.track({ + customer_id: customerId, + entity_id: entities[1].id, + feature_id: TestFeature.Messages, + value: 800, + }); + + const entity2Result = + await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.usage_alert_triggered" && + payload.data?.customer_id === customerId && + payload.data?.entity_id === entities[1].id && + payload.data?.usage_alert?.threshold === 700, + timeoutMs: 15000, + }); + + expect(entity2Result).not.toBeNull(); + expect(entity2Result!.payload.data.entity_id).toBe(entities[1].id); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 3: Customer-level alert still fires alongside entity-level alert +// ═══════════════════════════════════════════════════════════════════════════════ + +test(`${chalk.yellowBright("entity-alert3: customer-level and entity-level alerts both fire")}`, async () => { + const perEntityMessages = items.monthlyMessages({ + includedUsage: 500, + entityFeatureId: TestFeature.Users, + }); + const prod = products.base({ + id: "ea-both-levels-1", + items: [perEntityMessages], + }); + + const { customerId, autumnV2_1, entities } = await initScenario({ + customerId: "entity-alert-both-levels-1", + setup: [ + s.customer({ testClock: false }), + s.products({ list: [prod] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.attach({ productId: prod.id })], + }); + + // Set customer-level alert at 200 + await autumnV2_1.customers.update(customerId, { + billing_controls: { + usage_alerts: [ + { + feature_id: TestFeature.Messages, + threshold: 200, + threshold_type: "usage", + enabled: true, + }, + ], + }, + }); + + // Set entity-level alert at 300 + await autumnV2_1.entities.update(customerId, entities[0].id, { + billing_controls: { + usage_alerts: [ + { + feature_id: TestFeature.Messages, + threshold: 300, + threshold_type: "usage", + enabled: true, + }, + ], + } as EntityBillingControls, + }); + + // Track 350 on entity — crosses both 200 (customer) and 300 (entity) + await autumnV2_1.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 350, + }); + + // Customer-level alert (no entity_id in payload) + const customerResult = + await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.usage_alert_triggered" && + payload.data?.customer_id === customerId && + !payload.data?.entity_id && + payload.data?.usage_alert?.threshold === 200, + timeoutMs: 15000, + }); + + expect(customerResult).not.toBeNull(); + expect(customerResult!.payload.data.entity_id).toBeUndefined(); + expect(customerResult!.payload.data.usage_alert.threshold).toBe(200); + + // Entity-level alert (has entity_id in payload) + const entityResult = await waitForWebhook( + { + token: playToken, + predicate: (payload) => + payload.type === "balances.usage_alert_triggered" && + payload.data?.customer_id === customerId && + payload.data?.entity_id === entities[0].id && + payload.data?.usage_alert?.threshold === 300, + timeoutMs: 15000, + }, + ); + + expect(entityResult).not.toBeNull(); + expect(entityResult!.payload.data.entity_id).toBe(entities[0].id); + expect(entityResult!.payload.data.usage_alert.threshold).toBe(300); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 4: Entity percentage threshold alert +// ═══════════════════════════════════════════════════════════════════════════════ + +test(`${chalk.yellowBright("entity-alert4: entity percentage threshold fires correctly")}`, async () => { + const perEntityMessages = items.monthlyMessages({ + includedUsage: 200, + entityFeatureId: TestFeature.Users, + }); + const prod = products.base({ + id: "ea-pct-1", + items: [perEntityMessages], + }); + + const { customerId, autumnV2_1, entities } = await initScenario({ + customerId: "entity-alert-pct-1", + setup: [ + s.customer({ testClock: false }), + s.products({ list: [prod] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.attach({ productId: prod.id })], + }); + + // Set entity-level percentage alert at 80% + await autumnV2_1.entities.update(customerId, entities[0].id, { + billing_controls: { + usage_alerts: [ + { + feature_id: TestFeature.Messages, + threshold: 80, + threshold_type: "usage_percentage", + enabled: true, + }, + ], + } as EntityBillingControls, + }); + + // Track 170 usage on entity — 85% of 200, crosses 80% threshold + await autumnV2_1.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 170, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.usage_alert_triggered" && + payload.data?.customer_id === customerId && + payload.data?.entity_id === entities[0].id && + payload.data?.usage_alert?.threshold === 80, + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + const { data } = result!.payload; + expect(data.entity_id).toBe(entities[0].id); + expect(data.usage_alert.threshold).toBe(80); + expect(data.usage_alert.threshold_type).toBe("usage_percentage"); +}); + +// ═══════════════════════════════════════════════════════════════════════════════ +// TEST 5: Entity alert does not fire when below threshold +// ═══════════════════════════════════════════════════════════════════════════════ + +test(`${chalk.yellowBright("entity-alert5: entity alert does not fire when below threshold")}`, async () => { + const perEntityMessages = items.monthlyMessages({ + includedUsage: 1000, + entityFeatureId: TestFeature.Users, + }); + const prod = products.base({ + id: "ea-no-fire-1", + items: [perEntityMessages], + }); + + const { customerId, autumnV2_1, entities } = await initScenario({ + customerId: "entity-alert-no-fire-1", + setup: [ + s.customer({ testClock: false }), + s.products({ list: [prod] }), + s.entities({ count: 1, featureId: TestFeature.Users }), + ], + actions: [s.attach({ productId: prod.id })], + }); + + // Set entity-level alert at 800 + await autumnV2_1.entities.update(customerId, entities[0].id, { + billing_controls: { + usage_alerts: [ + { + feature_id: TestFeature.Messages, + threshold: 800, + threshold_type: "usage", + enabled: true, + }, + ], + } as EntityBillingControls, + }); + + // Track only 500 — below threshold + await autumnV2_1.track({ + customer_id: customerId, + entity_id: entities[0].id, + feature_id: TestFeature.Messages, + value: 500, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.usage_alert_triggered" && + payload.data?.customer_id === customerId && + payload.data?.entity_id === entities[0].id && + payload.data?.usage_alert?.threshold === 800, + timeoutMs: 8000, + }); + + expect(result).toBeNull(); +}); diff --git a/server/tests/integration/balances/utils/usage-alert-utils/svixUsageAlertEndpoint.ts b/server/tests/integration/balances/utils/usage-alert-utils/svixUsageAlertEndpoint.ts deleted file mode 100644 index 911a4e031..000000000 --- a/server/tests/integration/balances/utils/usage-alert-utils/svixUsageAlertEndpoint.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Svix endpoint utilities for usage alert webhook tests. - * Creates temporary endpoints filtered to `balances.threshold_reached` events. - */ - -import { Svix } from "svix"; - -let svixClient: Svix | null = null; - -const getSvixClient = (): Svix => { - if (!svixClient) { - const apiKey = process.env.SVIX_API_KEY; - if (!apiKey) { - throw new Error( - "SVIX_API_KEY environment variable is required for webhook tests", - ); - } - svixClient = new Svix(apiKey); - } - return svixClient; -}; - -export const createUsageAlertTestEndpoint = async ({ - appId, - playUrl, -}: { - appId: string; - playUrl: string; -}): Promise => { - const svix = getSvixClient(); - - const endpoint = await svix.endpoint.create(appId, { - url: playUrl, - description: "Test endpoint for usage alert webhook tests", - filterTypes: ["balances.threshold_reached"], - }); - - return endpoint.id; -}; - -export const deleteUsageAlertTestEndpoint = async ({ - appId, - endpointId, -}: { - appId: string; - endpointId: string; -}): Promise => { - const svix = getSvixClient(); - - try { - await svix.endpoint.delete(appId, endpointId); - } catch (error) { - console.warn(`Failed to delete test endpoint ${endpointId}:`, error); - } -}; diff --git a/server/tests/integration/billing/autumn-webhooks/attach-v1-webhooks.test.ts b/server/tests/integration/billing/autumn-webhooks/attach-v1-webhooks.test.ts index 5caf7d35f..5d417eb3e 100644 --- a/server/tests/integration/billing/autumn-webhooks/attach-v1-webhooks.test.ts +++ b/server/tests/integration/billing/autumn-webhooks/attach-v1-webhooks.test.ts @@ -18,21 +18,18 @@ import { expectProductCanceling, expectProductScheduled, } from "@tests/integration/billing/utils/expectCustomerProductCorrect"; +import { + getTestSvixAppId, + setupWebhookTest, + type WebhookTestSetup, + waitForWebhook, +} from "@tests/integration/utils/svixWebhookTestUtils.js"; import { TestFeature } from "@tests/setup/v2Features.js"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; import chalk from "chalk"; -import { - generatePlayToken, - getPlayWebhookUrl, - waitForWebhook, -} from "./utils/svixPlayClient.js"; -import { - createTestEndpoint, - deleteTestEndpoint, -} from "./utils/svixTestEndpoint.js"; type CustomerProductsUpdatedPayload = { type: string; @@ -48,37 +45,20 @@ type CustomerProductsUpdatedPayload = { // SVIX PLAY SETUP (shared across all tests) // ═══════════════════════════════════════════════════════════════════════════════ +let webhook: WebhookTestSetup; let playToken: string; -let endpointId: string; beforeAll(async () => { - // 1. Generate Svix Play token - playToken = await generatePlayToken(); - console.log(`Generated Svix Play token: ${playToken}`); - - // 2. Get org's Svix app ID - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (!svixAppId) { - throw new Error( - "Test org does not have svix_config.sandbox_app_id configured. " + - "Cannot run webhook integration tests without Svix app.", - ); - } - - // 3. Create Svix endpoint pointing to Svix Play - const playUrl = getPlayWebhookUrl(playToken); - console.log(`Creating Svix endpoint: ${playUrl}`); - endpointId = await createTestEndpoint({ appId: svixAppId, playUrl }); - console.log(`Created Svix endpoint: ${endpointId}`); + const appId = getTestSvixAppId({ svixConfig: ctx.org.svix_config }); + webhook = await setupWebhookTest({ + appId, + filterTypes: ["customer.products.updated"], + }); + playToken = webhook.playToken; }); afterAll(async () => { - // Cleanup: delete Svix endpoint - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (svixAppId && endpointId) { - await deleteTestEndpoint({ appId: svixAppId, endpointId }); - console.log(`Deleted Svix endpoint: ${endpointId}`); - } + await webhook?.cleanup(); }); // ═══════════════════════════════════════════════════════════════════════════════ diff --git a/server/tests/integration/billing/autumn-webhooks/attach-v2-webhooks.test.ts b/server/tests/integration/billing/autumn-webhooks/attach-v2-webhooks.test.ts index f064134b1..c3a3d641f 100644 --- a/server/tests/integration/billing/autumn-webhooks/attach-v2-webhooks.test.ts +++ b/server/tests/integration/billing/autumn-webhooks/attach-v2-webhooks.test.ts @@ -19,21 +19,18 @@ import { expectProductCanceling, expectProductScheduled, } from "@tests/integration/billing/utils/expectCustomerProductCorrect"; +import { + getTestSvixAppId, + setupWebhookTest, + type WebhookTestSetup, + waitForWebhook, +} from "@tests/integration/utils/svixWebhookTestUtils.js"; import { TestFeature } from "@tests/setup/v2Features.js"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; import chalk from "chalk"; -import { - generatePlayToken, - getPlayWebhookUrl, - waitForWebhook, -} from "./utils/svixPlayClient.js"; -import { - createTestEndpoint, - deleteTestEndpoint, -} from "./utils/svixTestEndpoint.js"; type CustomerProductsUpdatedPayload = { type: string; @@ -49,37 +46,20 @@ type CustomerProductsUpdatedPayload = { // SVIX PLAY SETUP (shared across all tests) // ═══════════════════════════════════════════════════════════════════════════════ +let webhook: WebhookTestSetup; let playToken: string; -let endpointId: string; beforeAll(async () => { - // 1. Generate Svix Play token - playToken = await generatePlayToken(); - console.log(`Generated Svix Play token: ${playToken}`); - - // 2. Get org's Svix app ID - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (!svixAppId) { - throw new Error( - "Test org does not have svix_config.sandbox_app_id configured. " + - "Cannot run webhook integration tests without Svix app.", - ); - } - - // 3. Create Svix endpoint pointing to Svix Play - const playUrl = getPlayWebhookUrl(playToken); - console.log(`Creating Svix endpoint: ${playUrl}`); - endpointId = await createTestEndpoint({ appId: svixAppId, playUrl }); - console.log(`Created Svix endpoint: ${endpointId}`); + const appId = getTestSvixAppId({ svixConfig: ctx.org.svix_config }); + webhook = await setupWebhookTest({ + appId, + filterTypes: ["customer.products.updated"], + }); + playToken = webhook.playToken; }); afterAll(async () => { - // Cleanup: delete Svix endpoint - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (svixAppId && endpointId) { - await deleteTestEndpoint({ appId: svixAppId, endpointId }); - console.log(`Deleted Svix endpoint: ${endpointId}`); - } + await webhook?.cleanup(); }); // ═══════════════════════════════════════════════════════════════════════════════ diff --git a/server/tests/integration/billing/autumn-webhooks/customer-products-updated.test.ts b/server/tests/integration/billing/autumn-webhooks/customer-products-updated.test.ts index acc788b1c..d8b2bfd0e 100644 --- a/server/tests/integration/billing/autumn-webhooks/customer-products-updated.test.ts +++ b/server/tests/integration/billing/autumn-webhooks/customer-products-updated.test.ts @@ -17,21 +17,18 @@ import { expectProductActive, expectProductCanceling, } from "@tests/integration/billing/utils/expectCustomerProductCorrect"; +import { + getTestSvixAppId, + setupWebhookTest, + type WebhookTestSetup, + waitForWebhook, +} from "@tests/integration/utils/svixWebhookTestUtils.js"; import { TestFeature } from "@tests/setup/v2Features.js"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; import chalk from "chalk"; -import { - generatePlayToken, - getPlayWebhookUrl, - waitForWebhook, -} from "./utils/svixPlayClient.js"; -import { - createTestEndpoint, - deleteTestEndpoint, -} from "./utils/svixTestEndpoint.js"; type CustomerProductsUpdatedPayload = { type: string; @@ -47,37 +44,20 @@ type CustomerProductsUpdatedPayload = { // SVIX PLAY SETUP (shared across all tests) // ═══════════════════════════════════════════════════════════════════════════════ +let webhook: WebhookTestSetup; let playToken: string; -let endpointId: string; beforeAll(async () => { - // 1. Generate Svix Play token - playToken = await generatePlayToken(); - console.log(`Generated Svix Play token: ${playToken}`); - - // 2. Get org's Svix app ID - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (!svixAppId) { - throw new Error( - "Test org does not have svix_config.sandbox_app_id configured. " + - "Cannot run webhook integration tests without Svix app.", - ); - } - - // 3. Create Svix endpoint pointing to Svix Play - const playUrl = getPlayWebhookUrl(playToken); - console.log(`Creating Svix endpoint: ${playUrl}`); - endpointId = await createTestEndpoint({ appId: svixAppId, playUrl }); - console.log(`Created Svix endpoint: ${endpointId}`); + const appId = getTestSvixAppId({ svixConfig: ctx.org.svix_config }); + webhook = await setupWebhookTest({ + appId, + filterTypes: ["customer.products.updated"], + }); + playToken = webhook.playToken; }); afterAll(async () => { - // Cleanup: delete Svix endpoint - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (svixAppId && endpointId) { - await deleteTestEndpoint({ appId: svixAppId, endpointId }); - console.log(`Deleted Svix endpoint: ${endpointId}`); - } + await webhook?.cleanup(); }); // ═══════════════════════════════════════════════════════════════════════════════ diff --git a/server/tests/integration/billing/autumn-webhooks/free-to-pro-upgrade-v1-webhook.test.ts b/server/tests/integration/billing/autumn-webhooks/free-to-pro-upgrade-v1-webhook.test.ts index 318ee2570..b826207ed 100644 --- a/server/tests/integration/billing/autumn-webhooks/free-to-pro-upgrade-v1-webhook.test.ts +++ b/server/tests/integration/billing/autumn-webhooks/free-to-pro-upgrade-v1-webhook.test.ts @@ -10,6 +10,12 @@ import { afterAll, beforeAll, expect, test } from "bun:test"; import type { ApiCustomerV3, ApiProduct } from "@autumn/shared"; import { expectProductActive } from "@tests/integration/billing/utils/expectCustomerProductCorrect"; +import { + getTestSvixAppId, + setupWebhookTest, + type WebhookTestSetup, + waitForWebhook, +} from "@tests/integration/utils/svixWebhookTestUtils.js"; import { completeStripeCheckoutFormV2 } from "@tests/utils/browserPool/completeStripeCheckoutFormV2.js"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; @@ -17,15 +23,6 @@ import { timeout } from "@tests/utils/genUtils.js"; import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; import chalk from "chalk"; -import { - generatePlayToken, - getPlayWebhookUrl, - waitForWebhook, -} from "./utils/svixPlayClient.js"; -import { - createTestEndpoint, - deleteTestEndpoint, -} from "./utils/svixTestEndpoint.js"; type CustomerProductsUpdatedPayload = { type: string; @@ -40,33 +37,20 @@ type CustomerProductsUpdatedPayload = { // SVIX PLAY SETUP // ═══════════════════════════════════════════════════════════════════════════════ +let webhook: WebhookTestSetup; let playToken: string; -let endpointId: string; beforeAll(async () => { - playToken = await generatePlayToken(); - console.log(`Generated Svix Play token: ${playToken}`); - - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (!svixAppId) { - throw new Error( - "Test org does not have svix_config.sandbox_app_id configured. " + - "Cannot run webhook integration tests without Svix app.", - ); - } - - const playUrl = getPlayWebhookUrl(playToken); - console.log(`Creating Svix endpoint: ${playUrl}`); - endpointId = await createTestEndpoint({ appId: svixAppId, playUrl }); - console.log(`Created Svix endpoint: ${endpointId}`); + const appId = getTestSvixAppId({ svixConfig: ctx.org.svix_config }); + webhook = await setupWebhookTest({ + appId, + filterTypes: ["customer.products.updated"], + }); + playToken = webhook.playToken; }); afterAll(async () => { - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (svixAppId && endpointId) { - await deleteTestEndpoint({ appId: svixAppId, endpointId }); - console.log(`Deleted Svix endpoint: ${endpointId}`); - } + await webhook?.cleanup(); }); // ═══════════════════════════════════════════════════════════════════════════════ diff --git a/server/tests/integration/billing/autumn-webhooks/free-to-pro-upgrade-v2-webhook.test.ts b/server/tests/integration/billing/autumn-webhooks/free-to-pro-upgrade-v2-webhook.test.ts index 620d28deb..62ff26d33 100644 --- a/server/tests/integration/billing/autumn-webhooks/free-to-pro-upgrade-v2-webhook.test.ts +++ b/server/tests/integration/billing/autumn-webhooks/free-to-pro-upgrade-v2-webhook.test.ts @@ -10,6 +10,12 @@ import { afterAll, beforeAll, expect, test } from "bun:test"; import type { ApiCustomerV3, ApiProduct } from "@autumn/shared"; import { expectProductActive } from "@tests/integration/billing/utils/expectCustomerProductCorrect"; +import { + getTestSvixAppId, + setupWebhookTest, + type WebhookTestSetup, + waitForWebhook, +} from "@tests/integration/utils/svixWebhookTestUtils.js"; import { completeStripeCheckoutFormV2 } from "@tests/utils/browserPool/completeStripeCheckoutFormV2.js"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; @@ -17,15 +23,6 @@ import { timeout } from "@tests/utils/genUtils.js"; import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; import chalk from "chalk"; -import { - generatePlayToken, - getPlayWebhookUrl, - waitForWebhook, -} from "./utils/svixPlayClient.js"; -import { - createTestEndpoint, - deleteTestEndpoint, -} from "./utils/svixTestEndpoint.js"; type CustomerProductsUpdatedPayload = { type: string; @@ -40,33 +37,20 @@ type CustomerProductsUpdatedPayload = { // SVIX PLAY SETUP // ═══════════════════════════════════════════════════════════════════════════════ +let webhook: WebhookTestSetup; let playToken: string; -let endpointId: string; beforeAll(async () => { - playToken = await generatePlayToken(); - console.log(`Generated Svix Play token: ${playToken}`); - - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (!svixAppId) { - throw new Error( - "Test org does not have svix_config.sandbox_app_id configured. " + - "Cannot run webhook integration tests without Svix app.", - ); - } - - const playUrl = getPlayWebhookUrl(playToken); - console.log(`Creating Svix endpoint: ${playUrl}`); - endpointId = await createTestEndpoint({ appId: svixAppId, playUrl }); - console.log(`Created Svix endpoint: ${endpointId}`); + const appId = getTestSvixAppId({ svixConfig: ctx.org.svix_config }); + webhook = await setupWebhookTest({ + appId, + filterTypes: ["customer.products.updated"], + }); + playToken = webhook.playToken; }); afterAll(async () => { - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (svixAppId && endpointId) { - await deleteTestEndpoint({ appId: svixAppId, endpointId }); - console.log(`Deleted Svix endpoint: ${endpointId}`); - } + await webhook?.cleanup(); }); // ═══════════════════════════════════════════════════════════════════════════════ diff --git a/server/tests/integration/billing/autumn-webhooks/update-subscription-webhooks.test.ts b/server/tests/integration/billing/autumn-webhooks/update-subscription-webhooks.test.ts index 448aeaa5e..e39f08c90 100644 --- a/server/tests/integration/billing/autumn-webhooks/update-subscription-webhooks.test.ts +++ b/server/tests/integration/billing/autumn-webhooks/update-subscription-webhooks.test.ts @@ -14,21 +14,18 @@ import { afterAll, beforeAll, expect, test } from "bun:test"; import type { ApiCustomerV3, ApiProduct } from "@autumn/shared"; import { expectCustomerFeatureCorrect } from "@tests/integration/billing/utils/expectCustomerFeatureCorrect"; import { expectProductActive } from "@tests/integration/billing/utils/expectCustomerProductCorrect"; +import { + getTestSvixAppId, + setupWebhookTest, + type WebhookTestSetup, + waitForWebhook, +} from "@tests/integration/utils/svixWebhookTestUtils.js"; import { TestFeature } from "@tests/setup/v2Features.js"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; import chalk from "chalk"; -import { - generatePlayToken, - getPlayWebhookUrl, - waitForWebhook, -} from "./utils/svixPlayClient.js"; -import { - createTestEndpoint, - deleteTestEndpoint, -} from "./utils/svixTestEndpoint.js"; type CustomerProductsUpdatedPayload = { type: string; @@ -43,37 +40,20 @@ type CustomerProductsUpdatedPayload = { // SVIX PLAY SETUP (shared across all tests) // ═══════════════════════════════════════════════════════════════════════════════ +let webhook: WebhookTestSetup; let playToken: string; -let endpointId: string; beforeAll(async () => { - // 1. Generate Svix Play token - playToken = await generatePlayToken(); - console.log(`Generated Svix Play token: ${playToken}`); - - // 2. Get org's Svix app ID - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (!svixAppId) { - throw new Error( - "Test org does not have svix_config.sandbox_app_id configured. " + - "Cannot run webhook integration tests without Svix app.", - ); - } - - // 3. Create Svix endpoint pointing to Svix Play - const playUrl = getPlayWebhookUrl(playToken); - console.log(`Creating Svix endpoint: ${playUrl}`); - endpointId = await createTestEndpoint({ appId: svixAppId, playUrl }); - console.log(`Created Svix endpoint: ${endpointId}`); + const appId = getTestSvixAppId({ svixConfig: ctx.org.svix_config }); + webhook = await setupWebhookTest({ + appId, + filterTypes: ["customer.products.updated"], + }); + playToken = webhook.playToken; }); afterAll(async () => { - // Cleanup: delete Svix endpoint - const svixAppId = ctx.org.svix_config?.sandbox_app_id; - if (svixAppId && endpointId) { - await deleteTestEndpoint({ appId: svixAppId, endpointId }); - console.log(`Deleted Svix endpoint: ${endpointId}`); - } + await webhook?.cleanup(); }); // ═══════════════════════════════════════════════════════════════════════════════ diff --git a/server/tests/integration/billing/autumn-webhooks/utils/svixPlayClient.ts b/server/tests/integration/billing/autumn-webhooks/utils/svixPlayClient.ts deleted file mode 100644 index 14a665608..000000000 --- a/server/tests/integration/billing/autumn-webhooks/utils/svixPlayClient.ts +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Svix Play API client for webhook testing. - * Uses the free Svix Play API - no signup required. - * - * API Docs: https://docs.svix.com/play#programmatic-use-of-the-public-api - */ - -const SVIX_PLAY_API_BASE = "https://api.play.svix.com/api/v1"; - -export type SvixPlayEvent = { - id: string; - url: string; - method: string; - created_at: string; - body: string; // base64 encoded - headers: Record; - response: { - status_code: number; - headers: Record; - body: string; - }; - ip: string | null; -}; - -export type SvixPlayHistory = { - iterator: string; - data: SvixPlayEvent[]; -}; - -/** - * Generate a new Svix Play token for webhook testing. - * Tokens are freely generated and don't require authentication. - */ -export const generatePlayToken = async (): Promise => { - const response = await fetch(`${SVIX_PLAY_API_BASE}/token/generate/`, { - method: "POST", - }); - - if (!response.ok) { - throw new Error(`Failed to generate Svix Play token: ${response.status}`); - } - - const data = (await response.json()) as { token: string }; - return data.token; -}; - -/** - * Get the webhook URL for a given Svix Play token. - * This URL receives webhooks and stores them for later inspection. - */ -export const getPlayWebhookUrl = (token: string): string => { - return `${SVIX_PLAY_API_BASE}/in/${token}/`; -}; - -/** - * Query the webhook history for a Svix Play token. - * Returns all webhooks received by this token. - */ -export const getPlayHistory = async ({ - token, - iterator, -}: { - token: string; - iterator?: string; -}): Promise => { - const url = new URL(`${SVIX_PLAY_API_BASE}/history/${token}/`); - if (iterator) { - url.searchParams.set("iterator", iterator); - } - - const response = await fetch(url.toString()); - - if (!response.ok) { - throw new Error(`Failed to get Svix Play history: ${response.status}`); - } - - return response.json() as Promise; -}; - -/** - * Parse a Svix Play event body (base64 → JSON). - */ -export const parseEventBody = (event: SvixPlayEvent): T => { - const decoded = Buffer.from(event.body, "base64").toString("utf-8"); - return JSON.parse(decoded) as T; -}; - -/** - * Wait for a webhook matching a predicate to appear in Svix Play. - * Polls every 500ms until timeout. - */ -export const waitForWebhook = async ({ - token, - predicate, - timeoutMs = 10000, -}: { - token: string; - predicate: (payload: T) => boolean; - timeoutMs?: number; -}): Promise<{ event: SvixPlayEvent; payload: T } | null> => { - const startTime = Date.now(); - - while (Date.now() - startTime < timeoutMs) { - const history = await getPlayHistory({ token }); - - for (const event of history.data) { - try { - const payload = parseEventBody(event); - if (predicate(payload)) { - return { event, payload }; - } - } catch { - // Skip events that can't be parsed - } - } - - await new Promise((resolve) => setTimeout(resolve, 500)); - } - - return null; -}; diff --git a/server/tests/integration/billing/autumn-webhooks/utils/svixTestEndpoint.ts b/server/tests/integration/billing/autumn-webhooks/utils/svixTestEndpoint.ts deleted file mode 100644 index 85a713d16..000000000 --- a/server/tests/integration/billing/autumn-webhooks/utils/svixTestEndpoint.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Utilities for managing Svix endpoints during webhook tests. - * Creates temporary endpoints pointing to Svix Play for test verification. - */ - -import { Svix } from "svix"; - -let svixClient: Svix | null = null; - -const getSvixClient = (): Svix => { - if (!svixClient) { - const apiKey = process.env.SVIX_API_KEY; - if (!apiKey) { - throw new Error( - "SVIX_API_KEY environment variable is required for webhook tests", - ); - } - svixClient = new Svix(apiKey); - } - return svixClient; -}; - -/** - * Create a test endpoint pointing to Svix Play. - * The endpoint will receive all webhook events from the org's Svix app. - */ -export const createTestEndpoint = async ({ - appId, - playUrl, -}: { - appId: string; - playUrl: string; -}): Promise => { - const svix = getSvixClient(); - - const endpoint = await svix.endpoint.create(appId, { - url: playUrl, - description: "Test endpoint for webhook integration tests", - filterTypes: ["customer.products.updated"], - }); - - return endpoint.id; -}; - -/** - * Delete a test endpoint after tests complete. - */ -export const deleteTestEndpoint = async ({ - appId, - endpointId, -}: { - appId: string; - endpointId: string; -}): Promise => { - const svix = getSvixClient(); - - try { - await svix.endpoint.delete(appId, endpointId); - } catch (error) { - // Log but don't fail if cleanup fails - console.warn(`Failed to delete test endpoint ${endpointId}:`, error); - } -}; diff --git a/server/tests/integration/crud/customers/customer-billing-controls.test.ts b/server/tests/integration/crud/customers/customer-billing-controls.test.ts index 5e97aa80d..2c4c22044 100644 --- a/server/tests/integration/crud/customers/customer-billing-controls.test.ts +++ b/server/tests/integration/crud/customers/customer-billing-controls.test.ts @@ -27,6 +27,17 @@ const autoTopupControls: CustomerBillingControls = { ], }; +const usageAlertControls: CustomerBillingControls = { + usage_alerts: [ + { + feature_id: TestFeature.Messages, + threshold: 90, + threshold_type: "usage_percentage", + enabled: true, + }, + ], +}; + test.concurrent(`${chalk.yellowBright("customer billing controls: create customer with spend limits")}`, async () => { const customerId = "customer-billing-controls-1"; const { autumnV2_1, ctx } = await initScenario({ @@ -170,3 +181,157 @@ test.concurrent(`${chalk.yellowBright("customer billing controls: reject duplica }), }); }); + +test.concurrent(`${chalk.yellowBright("customer billing controls: create customer with usage alerts")}`, async () => { + const customerId = "customer-billing-controls-4"; + const { autumnV2_1, ctx } = await initScenario({ + setup: [s.deleteCustomer({ customerId })], + actions: [], + }); + + await autumnV2_1.customers.create({ + id: customerId, + name: "Usage Alert Customer", + email: `${customerId}@example.com`, + billing_controls: usageAlertControls, + }); + + const cachedCustomer = + await autumnV2_1.customers.get(customerId); + expect(cachedCustomer.billing_controls?.usage_alerts).toEqual( + usageAlertControls.usage_alerts, + ); + + const uncachedCustomer = await autumnV2_1.customers.get( + customerId, + { skip_cache: "true" }, + ); + expect(uncachedCustomer.billing_controls?.usage_alerts).toEqual( + usageAlertControls.usage_alerts, + ); + + const fromDb = await CusService.getFull({ + ctx, + idOrInternalId: customerId, + }); + expect(fromDb.usage_alerts).toEqual(usageAlertControls.usage_alerts); +}); + +test.concurrent(`${chalk.yellowBright("customer billing controls: updating usage alerts does not unset spend limits or auto topups")}`, async () => { + const customerId = "customer-billing-controls-5"; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [s.customer({})], + actions: [], + }); + + // Set spend limits and auto topups first + await autumnV2_1.customers.update(customerId, { + billing_controls: spendLimitControls, + }); + await autumnV2_1.customers.update(customerId, { + billing_controls: autoTopupControls, + }); + + // Now update only usage_alerts + await autumnV2_1.customers.update(customerId, { + billing_controls: usageAlertControls, + }); + + const cached = await autumnV2_1.customers.get(customerId); + expect(cached.billing_controls?.spend_limits).toEqual( + spendLimitControls.spend_limits, + ); + expect(cached.billing_controls?.auto_topups).toEqual( + autoTopupControls.auto_topups, + ); + expect(cached.billing_controls?.usage_alerts).toEqual( + usageAlertControls.usage_alerts, + ); + + const uncached = await autumnV2_1.customers.get(customerId, { + skip_cache: "true", + }); + expect(uncached.billing_controls?.spend_limits).toEqual( + spendLimitControls.spend_limits, + ); + expect(uncached.billing_controls?.auto_topups).toEqual( + autoTopupControls.auto_topups, + ); + expect(uncached.billing_controls?.usage_alerts).toEqual( + usageAlertControls.usage_alerts, + ); +}); + +test.concurrent(`${chalk.yellowBright("customer billing controls: updating spend limits does not unset usage alerts")}`, async () => { + const customerId = "customer-billing-controls-6"; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [s.customer({})], + actions: [], + }); + + // Set usage alerts first + await autumnV2_1.customers.update(customerId, { + billing_controls: usageAlertControls, + }); + + // Now update only spend_limits + await autumnV2_1.customers.update(customerId, { + billing_controls: spendLimitControls, + }); + + const cached = await autumnV2_1.customers.get(customerId); + expect(cached.billing_controls?.usage_alerts).toEqual( + usageAlertControls.usage_alerts, + ); + expect(cached.billing_controls?.spend_limits).toEqual( + spendLimitControls.spend_limits, + ); + + const uncached = await autumnV2_1.customers.get(customerId, { + skip_cache: "true", + }); + expect(uncached.billing_controls?.usage_alerts).toEqual( + usageAlertControls.usage_alerts, + ); + expect(uncached.billing_controls?.spend_limits).toEqual( + spendLimitControls.spend_limits, + ); +}); + +test.concurrent(`${chalk.yellowBright("customer billing controls: clearing usage alerts with empty array")}`, async () => { + const customerId = "customer-billing-controls-7"; + const { autumnV2_1 } = await initScenario({ + customerId, + setup: [s.customer({})], + actions: [], + }); + + // Set both spend limits and usage alerts + await autumnV2_1.customers.update(customerId, { + billing_controls: { + ...spendLimitControls, + ...usageAlertControls, + }, + }); + + // Clear only usage_alerts + await autumnV2_1.customers.update(customerId, { + billing_controls: { usage_alerts: [] }, + }); + + const cached = await autumnV2_1.customers.get(customerId); + expect(cached.billing_controls?.usage_alerts).toEqual([]); + expect(cached.billing_controls?.spend_limits).toEqual( + spendLimitControls.spend_limits, + ); + + const uncached = await autumnV2_1.customers.get(customerId, { + skip_cache: "true", + }); + expect(uncached.billing_controls?.usage_alerts).toEqual([]); + expect(uncached.billing_controls?.spend_limits).toEqual( + spendLimitControls.spend_limits, + ); +}); diff --git a/server/tests/integration/crud/entities/update-entity-billing-controls.test.ts b/server/tests/integration/crud/entities/update-entity-billing-controls.test.ts index adc6727de..f3ecd781e 100644 --- a/server/tests/integration/crud/entities/update-entity-billing-controls.test.ts +++ b/server/tests/integration/crud/entities/update-entity-billing-controls.test.ts @@ -17,6 +17,17 @@ const initialBillingControls: EntityBillingControls = { ], }; +const usageAlertControls: EntityBillingControls = { + usage_alerts: [ + { + feature_id: TestFeature.Messages, + threshold: 80, + threshold_type: "usage_percentage", + enabled: true, + }, + ], +}; + test.concurrent(`${chalk.yellowBright("entity billing controls: create and update entity spend limits")}`, async () => { const { customerId, autumnV2_1 } = await initScenario({ customerId: "entity-billing-controls-1", @@ -171,3 +182,172 @@ test.concurrent(`${chalk.yellowBright("entity billing controls: reject duplicate }), }); }); + +test.concurrent(`${chalk.yellowBright("entity billing controls: create entity with usage alerts")}`, async () => { + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "entity-billing-controls-4", + setup: [s.customer({})], + actions: [], + }); + + const created = await autumnV2_1.entities.create(customerId, { + id: "entity-ua-1", + name: "Entity UA 1", + feature_id: TestFeature.Users, + billing_controls: usageAlertControls, + }); + + expect((created as ApiEntityV2).billing_controls?.usage_alerts).toEqual( + usageAlertControls.usage_alerts, + ); + + const fetched = await autumnV2_1.entities.get( + customerId, + "entity-ua-1", + ); + + expect(fetched.billing_controls?.usage_alerts).toEqual( + usageAlertControls.usage_alerts, + ); +}); + +test.concurrent(`${chalk.yellowBright("entity billing controls: create entity with both spend limits and usage alerts")}`, async () => { + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "entity-billing-controls-5", + setup: [s.customer({})], + actions: [], + }); + + const bothControls: EntityBillingControls = { + ...initialBillingControls, + ...usageAlertControls, + }; + + const created = await autumnV2_1.entities.create(customerId, { + id: "entity-both-1", + name: "Entity Both 1", + feature_id: TestFeature.Users, + billing_controls: bothControls, + }); + + expect((created as ApiEntityV2).billing_controls?.spend_limits).toEqual( + initialBillingControls.spend_limits, + ); + expect((created as ApiEntityV2).billing_controls?.usage_alerts).toEqual( + usageAlertControls.usage_alerts, + ); +}); + +test.concurrent(`${chalk.yellowBright("entity billing controls: updating usage alerts does not unset spend limits")}`, async () => { + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "entity-billing-controls-6", + setup: [s.customer({})], + actions: [], + }); + + await autumnV2_1.entities.create(customerId, { + id: "entity-preserve-1", + name: "Entity Preserve 1", + feature_id: TestFeature.Users, + billing_controls: initialBillingControls, + }); + + // Update only usage_alerts + await autumnV2_1.entities.update(customerId, "entity-preserve-1", { + billing_controls: usageAlertControls, + }); + + const fetched = await autumnV2_1.entities.get( + customerId, + "entity-preserve-1", + ); + + // spend_limits should be preserved + expect(fetched.billing_controls?.spend_limits).toEqual( + initialBillingControls.spend_limits, + ); + // usage_alerts should be set + expect(fetched.billing_controls?.usage_alerts).toEqual( + usageAlertControls.usage_alerts, + ); + + // Verify in DB too + const fromDb = await CusService.getFull({ + ctx, + idOrInternalId: customerId, + withEntities: true, + }); + const entity = fromDb.entities.find( + (candidate) => candidate.id === "entity-preserve-1", + ); + expect(entity?.spend_limits).toEqual(initialBillingControls.spend_limits); + expect(entity?.usage_alerts).toEqual(usageAlertControls.usage_alerts); +}); + +test.concurrent(`${chalk.yellowBright("entity billing controls: updating spend limits does not unset usage alerts")}`, async () => { + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "entity-billing-controls-7", + setup: [s.customer({})], + actions: [], + }); + + await autumnV2_1.entities.create(customerId, { + id: "entity-preserve-2", + name: "Entity Preserve 2", + feature_id: TestFeature.Users, + billing_controls: usageAlertControls, + }); + + // Update only spend_limits + await autumnV2_1.entities.update(customerId, "entity-preserve-2", { + billing_controls: initialBillingControls, + }); + + const fetched = await autumnV2_1.entities.get( + customerId, + "entity-preserve-2", + ); + + // usage_alerts should be preserved + expect(fetched.billing_controls?.usage_alerts).toEqual( + usageAlertControls.usage_alerts, + ); + // spend_limits should be set + expect(fetched.billing_controls?.spend_limits).toEqual( + initialBillingControls.spend_limits, + ); +}); + +test.concurrent(`${chalk.yellowBright("entity billing controls: clearing usage alerts with empty array")}`, async () => { + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "entity-billing-controls-8", + setup: [s.customer({})], + actions: [], + }); + + await autumnV2_1.entities.create(customerId, { + id: "entity-clear-ua-1", + name: "Entity Clear UA 1", + feature_id: TestFeature.Users, + billing_controls: { + ...initialBillingControls, + ...usageAlertControls, + }, + }); + + // Clear usage_alerts + await autumnV2_1.entities.update(customerId, "entity-clear-ua-1", { + billing_controls: { usage_alerts: [] }, + }); + + const fetched = await autumnV2_1.entities.get( + customerId, + "entity-clear-ua-1", + ); + + expect(fetched.billing_controls?.usage_alerts).toEqual([]); + // spend_limits should still be intact + expect(fetched.billing_controls?.spend_limits).toEqual( + initialBillingControls.spend_limits, + ); +}); diff --git a/server/tests/integration/utils/svixWebhookTestUtils.ts b/server/tests/integration/utils/svixWebhookTestUtils.ts new file mode 100644 index 000000000..d77a64006 --- /dev/null +++ b/server/tests/integration/utils/svixWebhookTestUtils.ts @@ -0,0 +1,214 @@ +/** + * Shared Svix webhook test utilities. + * + * Consolidates Svix Play client + endpoint management into one module + * so every webhook test file doesn't have to duplicate boilerplate. + */ + +import { Svix } from "svix"; + +// ─── Svix Admin Client ─────────────────────────────────────────────────────── + +let svixClient: Svix | null = null; + +const getSvixClient = (): Svix => { + if (!svixClient) { + const apiKey = process.env.SVIX_API_KEY; + if (!apiKey) + throw new Error( + "SVIX_API_KEY environment variable is required for webhook tests", + ); + svixClient = new Svix(apiKey); + } + return svixClient; +}; + +// ─── Svix Play Client ─────────────────────────────────────────────────────── + +const SVIX_PLAY_API_BASE = "https://api.play.svix.com/api/v1"; + +export type SvixPlayEvent = { + id: string; + url: string; + method: string; + created_at: string; + body: string; // base64 encoded + headers: Record; + response: { + status_code: number; + headers: Record; + body: string; + }; + ip: string | null; +}; + +export type SvixPlayHistory = { + iterator: string; + data: SvixPlayEvent[]; +}; + +export const generatePlayToken = async (): Promise => { + const response = await fetch(`${SVIX_PLAY_API_BASE}/token/generate/`, { + method: "POST", + }); + + if (!response.ok) + throw new Error(`Failed to generate Svix Play token: ${response.status}`); + + const data = (await response.json()) as { token: string }; + return data.token; +}; + +export const getPlayWebhookUrl = (token: string): string => { + return `${SVIX_PLAY_API_BASE}/in/${token}/`; +}; + +export const getPlayHistory = async ({ + token, + iterator, +}: { + token: string; + iterator?: string; +}): Promise => { + const url = new URL(`${SVIX_PLAY_API_BASE}/history/${token}/`); + if (iterator) url.searchParams.set("iterator", iterator); + + const response = await fetch(url.toString()); + + if (!response.ok) + throw new Error(`Failed to get Svix Play history: ${response.status}`); + + return response.json() as Promise; +}; + +export const parseEventBody = (event: SvixPlayEvent): T => { + const decoded = Buffer.from(event.body, "base64").toString("utf-8"); + return JSON.parse(decoded) as T; +}; + +/** Poll Svix Play until a webhook matching `predicate` appears, or timeout. */ +export const waitForWebhook = async ({ + token, + predicate, + timeoutMs = 10000, +}: { + token: string; + predicate: (payload: T) => boolean; + timeoutMs?: number; +}): Promise<{ event: SvixPlayEvent; payload: T } | null> => { + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutMs) { + const history = await getPlayHistory({ token }); + + for (const event of history.data) { + try { + const payload = parseEventBody(event); + if (predicate(payload)) return { event, payload }; + } catch { + // Skip events that can't be parsed + } + } + + await new Promise((resolve) => setTimeout(resolve, 500)); + } + + return null; +}; + +// ─── Endpoint Lifecycle ───────────────────────────────────────────────────── + +export const createWebhookTestEndpoint = async ({ + appId, + playUrl, + filterTypes, +}: { + appId: string; + playUrl: string; + filterTypes: string[]; +}): Promise => { + const svix = getSvixClient(); + + const endpoint = await svix.endpoint.create(appId, { + url: playUrl, + description: "Test endpoint for webhook integration tests", + filterTypes, + }); + + return endpoint.id; +}; + +export const deleteWebhookTestEndpoint = async ({ + appId, + endpointId, +}: { + appId: string; + endpointId: string; +}): Promise => { + const svix = getSvixClient(); + + try { + await svix.endpoint.delete(appId, endpointId); + } catch (error) { + console.warn(`Failed to delete test endpoint ${endpointId}:`, error); + } +}; + +// ─── High-Level Setup Helper ──────────────────────────────────────────────── + +export type WebhookTestSetup = { + playToken: string; + endpointId: string; + cleanup: () => Promise; +}; + +/** + * Full lifecycle helper: generates a Svix Play token, creates a filtered + * endpoint in the given app, and returns everything needed for tests + + * a `cleanup` function for `afterAll`. + */ +export const setupWebhookTest = async ({ + appId, + filterTypes, +}: { + appId: string; + filterTypes: string[]; +}): Promise => { + const playToken = await generatePlayToken(); + console.log(`Generated Svix Play token: ${playToken}`); + + const playUrl = getPlayWebhookUrl(playToken); + console.log(`Creating Svix endpoint: ${playUrl}`); + + const endpointId = await createWebhookTestEndpoint({ + appId, + playUrl, + filterTypes, + }); + console.log(`Created Svix endpoint: ${endpointId}`); + + const cleanup = async () => { + await deleteWebhookTestEndpoint({ appId, endpointId }); + console.log(`Deleted Svix endpoint: ${endpointId}`); + }; + + return { playToken, endpointId, cleanup }; +}; + +/** + * Resolves the org's Svix sandbox app ID or throws a clear error. + * Keeps the guard-clause out of every test file. + */ +export const getTestSvixAppId = ({ + svixConfig, +}: { + svixConfig?: { sandbox_app_id?: string } | null; +}): string => { + const appId = svixConfig?.sandbox_app_id; + if (!appId) + throw new Error( + "Test org does not have svix_config.sandbox_app_id configured. " + + "Cannot run webhook integration tests without Svix app.", + ); + return appId; +}; diff --git a/shared/api/billingControls/entityBillingControls.ts b/shared/api/billingControls/entityBillingControls.ts index 693f5b15a..179d63638 100644 --- a/shared/api/billingControls/entityBillingControls.ts +++ b/shared/api/billingControls/entityBillingControls.ts @@ -1,10 +1,14 @@ import { z } from "zod/v4"; import { ApiSpendLimitSchema } from "./spendLimit.js"; +import { ApiUsageAlertSchema } from "./usageAlert.js"; export const ApiEntityBillingControlsSchema = z.object({ spend_limits: z.array(ApiSpendLimitSchema).optional().meta({ description: "List of overage spend limits per feature.", }), + usage_alerts: z.array(ApiUsageAlertSchema).optional().meta({ + description: "List of usage alert configurations per feature.", + }), }); export const ApiEntityBillingControlsParamsSchema = diff --git a/shared/api/customers/cusFeatures/utils/check/getFeatureToUseForCheck.ts b/shared/api/customers/cusFeatures/utils/check/getFeatureToUseForCheck.ts index 1fbba4000..378015d2e 100644 --- a/shared/api/customers/cusFeatures/utils/check/getFeatureToUseForCheck.ts +++ b/shared/api/customers/cusFeatures/utils/check/getFeatureToUseForCheck.ts @@ -30,7 +30,7 @@ export const getFeatureToUseForCheck = ({ apiSubject, feature, requiredBalance, - }) + }).allowed ) { return feature; } @@ -45,7 +45,7 @@ export const getFeatureToUseForCheck = ({ apiSubject, feature: creditSystem, requiredBalance, - }) + }).allowed ) { return creditSystem; } diff --git a/shared/api/customers/cusFeatures/utils/convert/apiBalanceToAllowed.ts b/shared/api/customers/cusFeatures/utils/convert/apiBalanceToAllowed.ts index 449d83dc7..f5880d406 100644 --- a/shared/api/customers/cusFeatures/utils/convert/apiBalanceToAllowed.ts +++ b/shared/api/customers/cusFeatures/utils/convert/apiBalanceToAllowed.ts @@ -5,58 +5,53 @@ import type { Feature } from "@models/featureModels/featureModels"; import { isBooleanFeature, notNullish } from "@utils/index"; import { Decimal } from "decimal.js"; +export type AllowedResult = { + allowed: boolean; + limitType?: "included" | "max_purchase" | "spend_limit"; +}; + +export type ApiBalanceInput = { + apiBalance: ApiBalanceV1; + apiSubject: ApiSubjectV0; + feature: Feature; + requiredBalance: number; +}; + export const apiBalanceToAllowed = ({ apiBalance, apiSubject, feature, requiredBalance, -}: { - apiBalance: ApiBalanceV1; - apiSubject: ApiSubjectV0; - feature: Feature; - requiredBalance: number; -}) => { - if (!apiBalance) { - return false; - } +}: ApiBalanceInput): AllowedResult => { + if (!apiBalance) return { allowed: false }; - // 1. Boolean - if (isBooleanFeature({ feature })) { - return true; - } + if (isBooleanFeature({ feature })) return { allowed: true }; - // 2. Unlimited - if (apiBalance.unlimited) { - return true; - } + if (apiBalance.unlimited) return { allowed: true }; - // 2. Required balance is negative - if (requiredBalance < 0) { - return true; - } + if (requiredBalance < 0) return { allowed: true }; - // 3. Overage allowed if (apiBalance.overage_allowed) { - // 1. Available overage - const availableOverage = apiBalanceV1ToAvailableOverage({ + const { availableOverage, reason } = apiBalanceV1ToAvailableOverage({ apiBalance, apiSubject, feature, }); if (notNullish(availableOverage)) { - return new Decimal(availableOverage) + const allowed = new Decimal(availableOverage) .add(apiBalance.remaining) .gte(requiredBalance); + + if (!allowed) return { allowed: false, limitType: reason }; + return { allowed: true }; } - return true; + return { allowed: true }; } - // 4. Balance >= required balance (V1 uses 'remaining' instead of 'current_balance') - if (new Decimal(apiBalance.remaining).gte(requiredBalance)) { - return true; - } + if (new Decimal(apiBalance.remaining).gte(requiredBalance)) + return { allowed: true }; - return false; + return { allowed: false, limitType: "included" }; }; diff --git a/shared/api/customers/cusFeatures/utils/convert/apiBalanceV1ToAvailableOverage.ts b/shared/api/customers/cusFeatures/utils/convert/apiBalanceV1ToAvailableOverage.ts index ced0cbd19..586ee9d0b 100644 --- a/shared/api/customers/cusFeatures/utils/convert/apiBalanceV1ToAvailableOverage.ts +++ b/shared/api/customers/cusFeatures/utils/convert/apiBalanceV1ToAvailableOverage.ts @@ -39,6 +39,11 @@ export const apiBalanceV1ToMaxOverage = ({ ); }; +export type AvailableOverageResult = { + availableOverage: number | undefined; + reason?: "spend_limit" | "max_purchase"; +}; + export const apiBalanceV1ToAvailableOverage = ({ apiBalance, apiSubject, @@ -47,7 +52,7 @@ export const apiBalanceV1ToAvailableOverage = ({ apiBalance: ApiBalanceV1; apiSubject: ApiSubjectV0; feature: Feature; -}): number | undefined => { +}): AvailableOverageResult => { const overage = apiBalanceV1ToOverage({ apiBalance }); const spendLimit: ApiSpendLimit | undefined = apiSubject ? apiSubjectToSpendLimit({ @@ -57,17 +62,26 @@ export const apiBalanceV1ToAvailableOverage = ({ : undefined; if (spendLimit?.overage_limit !== undefined) { - return Math.max( - 0, - new Decimal(spendLimit.overage_limit).sub(overage).toNumber(), - ); + return { + availableOverage: Math.max( + 0, + new Decimal(spendLimit.overage_limit).sub(overage).toNumber(), + ), + reason: "spend_limit", + }; } const maxOverage = apiBalanceV1ToMaxOverage({ apiBalance }); if (maxOverage === undefined) { - return undefined; + return { availableOverage: undefined }; } - return Math.max(0, new Decimal(maxOverage).sub(overage).toNumber()); + return { + availableOverage: Math.max( + 0, + new Decimal(maxOverage).sub(overage).toNumber(), + ), + reason: "max_purchase", + }; }; diff --git a/shared/api/webhooks/balances/balancesLimitReached.ts b/shared/api/webhooks/balances/balancesLimitReached.ts new file mode 100644 index 000000000..489fb1cc3 --- /dev/null +++ b/shared/api/webhooks/balances/balancesLimitReached.ts @@ -0,0 +1,33 @@ +import { z } from "zod/v4"; + +export const LimitType = z.enum(["included", "max_purchase", "spend_limit"]); + +export const BALANCES_LIMIT_REACHED_EXAMPLE = { + customer_id: "org_123", + entity_id: "workspace_abc", + feature_id: "api_calls", + limit_type: "included", +}; + +export const BalancesLimitReachedSchema = z + .object({ + customer_id: z.string().meta({ + description: "The ID of the customer who hit the limit.", + }), + entity_id: z.string().optional().meta({ + description: + "The entity ID, if the limit was reached on a specific entity.", + }), + feature_id: z.string().meta({ + description: "The feature ID whose limit was reached.", + }), + limit_type: LimitType.meta({ + description: + "Which limit was hit: included allowance, max purchase cap, or spend limit.", + }), + }) + .meta({ + examples: [BALANCES_LIMIT_REACHED_EXAMPLE], + }); + +export type BalancesLimitReached = z.infer; diff --git a/shared/api/webhooks/balances/balancesUsageAlertTriggered.ts b/shared/api/webhooks/balances/balancesUsageAlertTriggered.ts new file mode 100644 index 000000000..1f9a65c00 --- /dev/null +++ b/shared/api/webhooks/balances/balancesUsageAlertTriggered.ts @@ -0,0 +1,53 @@ +import { z } from "zod/v4"; +import { UsageAlertThresholdType } from "../../billingControls/usageAlert.js"; + +export const BalancesUsageAlertTriggeredAlertSchema = z.object({ + name: z.string().optional().meta({ + description: "User-defined label for the alert, if provided.", + }), + threshold: z.number().meta({ + description: "The threshold value that was crossed.", + }), + threshold_type: UsageAlertThresholdType.meta({ + description: + "Whether the threshold is an absolute usage count or a percentage.", + }), +}); + +export const BALANCES_USAGE_ALERT_TRIGGERED_EXAMPLE = { + customer_id: "org_123", + feature_id: "api_calls", + entity_id: "workspace_abc", + usage_alert: { + name: "80% usage warning", + threshold: 80, + threshold_type: "usage_percentage_threshold", + }, +}; + +export const BalancesUsageAlertTriggeredSchema = z + .object({ + customer_id: z.string().meta({ + description: "The ID of the customer whose usage alert was triggered.", + }), + feature_id: z.string().meta({ + description: "The feature ID the alert applies to.", + }), + entity_id: z.string().optional().meta({ + description: + "The entity ID the alert applies to, if the usage was entity-scoped.", + }), + usage_alert: BalancesUsageAlertTriggeredAlertSchema.meta({ + description: "Details of the usage alert that was triggered.", + }), + }) + .meta({ + examples: [BALANCES_USAGE_ALERT_TRIGGERED_EXAMPLE], + }); + +export type BalancesUsageAlertTriggered = z.infer< + typeof BalancesUsageAlertTriggeredSchema +>; +export type BalancesUsageAlertTriggeredAlert = z.infer< + typeof BalancesUsageAlertTriggeredAlertSchema +>; diff --git a/shared/api/webhooks/balancesThresholdReached.ts b/shared/api/webhooks/balancesThresholdReached.ts deleted file mode 100644 index 9e32d48cd..000000000 --- a/shared/api/webhooks/balancesThresholdReached.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { z } from "zod/v4"; -import { UsageAlertThresholdType } from "../billingControls/usageAlert.js"; - -export const BalancesThresholdType = z.enum([ - "usage_alert", - "allowance_used", - "limit_reached", -]); - -export const BalancesThresholdReachedUsageAlertSchema = z.object({ - name: z.string().optional().meta({ - description: "User-defined label for the alert, if provided.", - }), - threshold: z.number().meta({ - description: "The threshold value that was crossed.", - }), - threshold_type: UsageAlertThresholdType.meta({ - description: - "Whether the threshold is an absolute usage count or a percentage.", - }), -}); - -export const BalancesThresholdReachedSchema = z - .object({ - customer_id: z.string().meta({ - description: "The ID of the customer whose threshold was reached.", - }), - feature_id: z.string().meta({ - description: "The feature ID the threshold applies to.", - }), - threshold_type: BalancesThresholdType.meta({ - description: "The type of threshold that was reached.", - }), - usage_alert: BalancesThresholdReachedUsageAlertSchema.optional().meta({ - description: - "Details of the usage alert that was triggered. Required when threshold_type is usage_alert.", - }), - }) - .check((ctx) => { - const { threshold_type, usage_alert } = ctx.value; - - if (threshold_type === "usage_alert" && !usage_alert) { - ctx.issues.push({ - code: "custom", - input: ctx.value, - message: - "usage_alert is required when threshold_type is usage_alert", - path: ["usage_alert"], - }); - return; - } - - if (threshold_type !== "usage_alert" && usage_alert) { - ctx.issues.push({ - code: "custom", - input: ctx.value, - message: - "usage_alert must not be provided when threshold_type is not usage_alert", - path: ["usage_alert"], - }); - } - }); - -export type BalancesThresholdReached = z.infer< - typeof BalancesThresholdReachedSchema ->; -export type BalancesThresholdReachedUsageAlert = z.infer< - typeof BalancesThresholdReachedUsageAlertSchema ->; diff --git a/shared/api/webhooks/index.ts b/shared/api/webhooks/index.ts index 402494dda..d44177ee6 100644 --- a/shared/api/webhooks/index.ts +++ b/shared/api/webhooks/index.ts @@ -1 +1,5 @@ -export * from "./balancesThresholdReached.js"; +export * from "./balances/balancesLimitReached.js"; +export * from "./balances/balancesUsageAlertTriggered.js"; +export * from "./vercel/index.js"; +export * from "./webhookEventType.js"; +export * from "./webhookRegistry.js"; diff --git a/shared/api/webhooks/vercel/index.ts b/shared/api/webhooks/vercel/index.ts new file mode 100644 index 000000000..7bd43c800 --- /dev/null +++ b/shared/api/webhooks/vercel/index.ts @@ -0,0 +1,4 @@ +export * from "./vercelResourceDeleted.js"; +export * from "./vercelResourceProvisioned.js"; +export * from "./vercelResourceRotateSecrets.js"; +export * from "./vercelWebhookEvent.js"; diff --git a/shared/api/webhooks/vercel/vercelResourceDeleted.ts b/shared/api/webhooks/vercel/vercelResourceDeleted.ts new file mode 100644 index 000000000..33dfc1074 --- /dev/null +++ b/shared/api/webhooks/vercel/vercelResourceDeleted.ts @@ -0,0 +1,14 @@ +import { z } from "zod/v4"; + +export const VercelResourceDeletedSchema = z.object({ + resource: z + .object({ + id: z.string().meta({ + description: "The unique identifier of the deleted resource.", + }), + }) + .meta({ description: "The resource that was deleted." }), + installation_id: z.string().meta({ + description: "The Vercel integration configuration ID.", + }), +}); diff --git a/shared/api/webhooks/vercel/vercelResourceProvisioned.ts b/shared/api/webhooks/vercel/vercelResourceProvisioned.ts new file mode 100644 index 000000000..8a4ce4679 --- /dev/null +++ b/shared/api/webhooks/vercel/vercelResourceProvisioned.ts @@ -0,0 +1,21 @@ +import { z } from "zod/v4"; + +export const VercelResourceProvisionedSchema = z.object({ + resource: z + .object({ + id: z.string().meta({ + description: "The unique identifier of the provisioned resource.", + }), + name: z.string().meta({ + description: "The display name of the provisioned resource.", + }), + }) + .meta({ description: "The resource that was provisioned." }), + installation_id: z.string().meta({ + description: "The Vercel integration configuration ID.", + }), + access_token: z.string().meta({ + description: + "An access token that can be used to patch the resource's secrets.", + }), +}); diff --git a/shared/api/webhooks/vercel/vercelResourceRotateSecrets.ts b/shared/api/webhooks/vercel/vercelResourceRotateSecrets.ts new file mode 100644 index 000000000..003847618 --- /dev/null +++ b/shared/api/webhooks/vercel/vercelResourceRotateSecrets.ts @@ -0,0 +1,17 @@ +import { z } from "zod/v4"; + +export const VercelResourceRotateSecretsSchema = z.object({ + resource: z + .object({ + id: z.string().meta({ + description: "The unique identifier of the resource.", + }), + }) + .meta({ description: "The resource whose secrets should be rotated." }), + installation_id: z.string().meta({ + description: "The Vercel integration configuration ID.", + }), + vercel_request_body: z.any().meta({ + description: "The raw request body from Vercel's rotation request.", + }), +}); diff --git a/shared/api/webhooks/vercel/vercelWebhookEvent.ts b/shared/api/webhooks/vercel/vercelWebhookEvent.ts new file mode 100644 index 000000000..4f846c3d8 --- /dev/null +++ b/shared/api/webhooks/vercel/vercelWebhookEvent.ts @@ -0,0 +1,10 @@ +import { z } from "zod/v4"; + +export const VercelWebhookEventSchema = z.object({ + installation_id: z.string().meta({ + description: "The Vercel integration configuration ID.", + }), + event: z.any().meta({ + description: "The raw Vercel webhook event payload.", + }), +}); diff --git a/shared/api/webhooks/webhookEventType.ts b/shared/api/webhooks/webhookEventType.ts new file mode 100644 index 000000000..377c78986 --- /dev/null +++ b/shared/api/webhooks/webhookEventType.ts @@ -0,0 +1,12 @@ +export enum WebhookEventType { + CustomerProductsUpdated = "customer.products.updated", + CustomerThresholdReached = "customer.threshold_reached", + + BalancesUsageAlertTriggered = "balances.usage_alert_triggered", + BalancesLimitReached = "balances.limit_reached", + + VercelResourcesDeleted = "vercel.resources.deleted", + VercelResourcesProvisioned = "vercel.resources.provisioned", + VercelResourcesRotateSecrets = "vercel.resources.rotate_secrets", + VercelWebhooksEvent = "vercel.webhooks.event", +} diff --git a/shared/api/webhooks/webhookRegistry.ts b/shared/api/webhooks/webhookRegistry.ts new file mode 100644 index 000000000..bee687f7a --- /dev/null +++ b/shared/api/webhooks/webhookRegistry.ts @@ -0,0 +1,83 @@ +import type { z } from "zod/v4"; +import { BalancesLimitReachedSchema } from "./balances/balancesLimitReached.js"; +import { BalancesUsageAlertTriggeredSchema } from "./balances/balancesUsageAlertTriggered.js"; +import { VercelResourceDeletedSchema } from "./vercel/vercelResourceDeleted.js"; +import { VercelResourceProvisionedSchema } from "./vercel/vercelResourceProvisioned.js"; +import { VercelResourceRotateSecretsSchema } from "./vercel/vercelResourceRotateSecrets.js"; +import { VercelWebhookEventSchema } from "./vercel/vercelWebhookEvent.js"; +import { WebhookEventType } from "./webhookEventType.js"; + +export interface WebhookDefinition { + eventType: string; + operationId: string; + title: string; + schema?: z.ZodType; + description: string; + group: string; + deprecated?: boolean; + archived?: boolean; + featureFlags?: string[]; +} + +export const webhookRegistry: WebhookDefinition[] = [ + // ── Balances ────────────────────────────────────────────────────────── + { + eventType: WebhookEventType.BalancesUsageAlertTriggered, + operationId: "balancesUsageAlertTriggered", + title: "Usage Alert Triggered", + schema: BalancesUsageAlertTriggeredSchema, + group: "Balances", + description: + "Fired when a customer crosses a configured usage alert threshold.", + }, + { + eventType: WebhookEventType.BalancesLimitReached, + operationId: "balancesLimitReached", + title: "Limit Reached", + schema: BalancesLimitReachedSchema, + group: "Balances", + description: + "Fired when a customer reaches the limit for a feature (included allowance, max purchase, or spend limit).", + }, + + // ── Vercel ──────────────────────────────────────────────────────────── + { + eventType: WebhookEventType.VercelResourcesDeleted, + operationId: "vercelResourcesDeleted", + title: "Resource Deleted", + schema: VercelResourceDeletedSchema, + group: "Vercel", + description: + "When a Vercel resource is deleted, you'll need to handle de-provisioning any API keys or other non-Autumn controlled data for this user.", + featureFlags: ["vercel"], + }, + { + eventType: WebhookEventType.VercelResourcesProvisioned, + operationId: "vercelResourcesProvisioned", + title: "Resource Provisioned", + schema: VercelResourceProvisionedSchema, + group: "Vercel", + description: + "When a Vercel resource is created, you'll need to provision a secret key for your service. Then you can use the provided access token to patch the resource's secrets.", + featureFlags: ["vercel"], + }, + { + eventType: WebhookEventType.VercelResourcesRotateSecrets, + operationId: "vercelResourcesRotateSecrets", + title: "Rotate Secrets", + schema: VercelResourceRotateSecretsSchema, + group: "Vercel", + description: + "This event is sent when Vercel requires a resource's secrets to be rotated.", + featureFlags: ["vercel"], + }, + { + eventType: WebhookEventType.VercelWebhooksEvent, + operationId: "vercelWebhooksEvent", + title: "Webhook Event", + schema: VercelWebhookEventSchema, + group: "Vercel", + description: "Passthrough webhook for Vercel events.", + featureFlags: ["vercel"], + }, +]; diff --git a/shared/enums/WebhookEventType.ts b/shared/enums/WebhookEventType.ts deleted file mode 100644 index 10b1f149c..000000000 --- a/shared/enums/WebhookEventType.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum WebhookEventType { - CustomerProductsUpdated = "customer.products.updated", - CustomerThresholdReached = "customer.threshold_reached", - BalancesThresholdReached = "balances.threshold_reached", -} diff --git a/shared/index.ts b/shared/index.ts index 5b8cd450f..8beded914 100644 --- a/shared/index.ts +++ b/shared/index.ts @@ -30,8 +30,7 @@ export * from "./enums/ErrCode"; export * from "./enums/LoggerAction"; // ENUMS export * from "./enums/SuccessCode"; -export * from "./enums/WebhookEventType"; -// Webhook Schemas +// Webhook Schemas + WebhookEventType enum export * from "./api/webhooks/index"; // Internal API (checkout app, dashboard) export * from "./internal/index"; diff --git a/shared/models/cusModels/billingControls/entityBillingControls.ts b/shared/models/cusModels/billingControls/entityBillingControls.ts index afde88943..d815fcd2a 100644 --- a/shared/models/cusModels/billingControls/entityBillingControls.ts +++ b/shared/models/cusModels/billingControls/entityBillingControls.ts @@ -1,10 +1,14 @@ import { z } from "zod/v4"; import { DbSpendLimitSchema } from "./spendLimit.js"; +import { DbUsageAlertSchema } from "./usageAlert.js"; export const EntityBillingControlsSchema = z.object({ spend_limits: z.array(DbSpendLimitSchema).optional().meta({ description: "List of overage spend limits per feature.", }), + usage_alerts: z.array(DbUsageAlertSchema).optional().meta({ + description: "List of usage alert configurations per feature.", + }), }); export type EntityBillingControls = z.infer; diff --git a/shared/models/cusModels/billingControls/usageAlert.ts b/shared/models/cusModels/billingControls/usageAlert.ts index f47fe5c29..e838e5dd3 100644 --- a/shared/models/cusModels/billingControls/usageAlert.ts +++ b/shared/models/cusModels/billingControls/usageAlert.ts @@ -1,8 +1,8 @@ import { z } from "zod/v4"; export const UsageAlertThresholdType = z.enum([ - "usage_threshold", - "usage_percentage_threshold", + "usage", + "usage_percentage", ]); export const DbUsageAlertSchema = z @@ -16,7 +16,7 @@ export const DbUsageAlertSchema = z }), threshold: z.number().min(0).meta({ description: - "The threshold value that triggers the alert. For usage_threshold, this is an absolute count. For usage_percentage_threshold, this is a percentage (0-100).", + "The threshold value that triggers the alert. For usage, this is an absolute count. For usage_percentage, this is a percentage (0-100).", }), threshold_type: UsageAlertThresholdType.meta({ description: @@ -30,13 +30,13 @@ export const DbUsageAlertSchema = z .check((ctx) => { const { threshold_type, threshold } = ctx.value; - if (threshold_type === "usage_percentage_threshold" && threshold > 100) { + if (threshold_type === "usage_percentage" && threshold > 100) { ctx.issues.push({ code: "custom", input: threshold, path: ["threshold"], message: - "Threshold must be between 0 and 100 for usage_percentage_threshold", + "Threshold must be between 0 and 100 for usage_percentage", }); } }); diff --git a/shared/models/cusModels/entityModels/entityModels.ts b/shared/models/cusModels/entityModels/entityModels.ts index f63ffbace..25c8413f0 100644 --- a/shared/models/cusModels/entityModels/entityModels.ts +++ b/shared/models/cusModels/entityModels/entityModels.ts @@ -1,6 +1,9 @@ import { z } from "zod/v4"; import type { Feature } from "../../featureModels/featureModels.js"; -import { DbSpendLimitSchema } from "../billingControls/customerBillingControls.js"; +import { + DbSpendLimitSchema, + DbUsageAlertSchema, +} from "../billingControls/customerBillingControls.js"; export const EntitySchema = z.object({ id: z.string().nullable(), @@ -14,6 +17,7 @@ export const EntitySchema = z.object({ feature_id: z.string(), internal_feature_id: z.string(), spend_limits: z.array(DbSpendLimitSchema).nullish(), + usage_alerts: z.array(DbUsageAlertSchema).nullish(), }); // export const CreateEntitySchema = z.object({ diff --git a/shared/models/cusModels/entityModels/entityTable.ts b/shared/models/cusModels/entityModels/entityTable.ts index 9715bef54..5cfe847b6 100644 --- a/shared/models/cusModels/entityModels/entityTable.ts +++ b/shared/models/cusModels/entityModels/entityTable.ts @@ -11,7 +11,10 @@ import { } from "drizzle-orm/pg-core"; import { features } from "../../featureModels/featureTable.js"; import { organizations } from "../../orgModels/orgTable.js"; -import type { DbSpendLimit } from "../billingControls/customerBillingControls.js"; +import type { + DbSpendLimit, + DbUsageAlert, +} from "../billingControls/customerBillingControls.js"; import { customers } from "../cusTable.js"; export const entities = pgTable( @@ -27,6 +30,7 @@ export const entities = pgTable( deleted: boolean().default(false).notNull(), internal_feature_id: text("internal_feature_id"), spend_limits: jsonb().$type(), + usage_alerts: jsonb().$type(), // Optional... feature_id: text("feature_id"), From a7a2ad98dee96901135ed61472c68d1c4ae0dcf5 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 23 Mar 2026 16:56:43 +0000 Subject: [PATCH 37/49] updated docs with usage alert guides --- .claude/rules/sdk.mdc | 35 ++- apps/docs/mintlify/changelog/changelog.mdx | 8 + .../modelling-pricing/spend-limits.mdx | 240 +++++++++++++++++- .../legacy/legacy-update-balance.test.ts | 6 +- .../lock/check-with-lock-edge-cases.test.ts | 4 + 5 files changed, 285 insertions(+), 8 deletions(-) diff --git a/.claude/rules/sdk.mdc b/.claude/rules/sdk.mdc index e68ee9245..f839a696e 100644 --- a/.claude/rules/sdk.mdc +++ b/.claude/rules/sdk.mdc @@ -6,6 +6,8 @@ alwaysApply: true Zod Schemas → ORPC Contracts → OpenAPI Spec → Speakeasy SDKs → autumn-js Wrapper → React Hooks +Webhook Zod Schemas → Webhook Registry → OpenAPI Spec (injected) + Svix Event Types + Run `bun api` from root to execute the full pipeline. --- @@ -152,7 +154,32 @@ When writing documentation examples for React hooks or the TypeScript SDK, alway --- -## 6. Testing (sdk-test) +## 6. Webhook Pipeline + +**Registry:** `shared/api/webhooks/webhookRegistry.ts` + +Each webhook is defined as a `WebhookDefinition` with `eventType`, `operationId`, `title`, `schema` (Zod), `description`, and `group`. Schemas live in `shared/api/webhooks/`. + +### OpenAPI injection + +`packages/openapi/v2.1/webhooks/injectWebhooks.ts` reads the registry and injects `webhooks` entries into the generated OpenAPI document. Only definitions with a Zod schema are included. + +### Svix sync + +`packages/openapi/scripts/svixPush.ts` pushes event types to Svix (create or update). It does **not** delete event types missing from the registry. + +### Adding a new webhook event + +1. **Create schema** in `shared/api/webhooks/` (Zod schema with `.meta()` descriptions) +2. **Add to registry** in `shared/api/webhooks/webhookRegistry.ts` +3. **Add to `WebhookEventType`** enum in `shared/api/webhooks/webhookEventType.ts` +4. **Re-export** from `shared/api/webhooks/index.ts` +5. **Regenerate** via `bun api` (webhooks are injected into the OpenAPI spec automatically) +6. **Push to Svix** via `bun run --cwd packages/openapi scripts/svixPush.ts` + +--- + +## 7. Testing (sdk-test) **Location:** `apps/sdk-test/` @@ -160,7 +187,7 @@ Scenario pages organized by provider: `scenarios/core/`, `scenarios/better-auth/ --- -## Adding a New Route +## Adding a New API Route 1. **Add contract** in `packages/openapi/v2.1/contracts/` 2. **Add to router** in `packages/openapi/v2.1/contracts/index.ts` @@ -184,3 +211,7 @@ Scenario pages organized by provider: `scenarios/core/`, `scenarios/better-auth/ | React client | `packages/autumn-js/src/react/client/AutumnClient.ts` | | React hooks | `packages/autumn-js/src/react/hooks/` | | SDK tests | `apps/sdk-test/` | +| Webhook schemas | `shared/api/webhooks/` | +| Webhook registry | `shared/api/webhooks/webhookRegistry.ts` | +| Webhook OpenAPI injection | `packages/openapi/v2.1/webhooks/injectWebhooks.ts` | +| Svix push script | `packages/openapi/scripts/svixPush.ts` | diff --git a/apps/docs/mintlify/changelog/changelog.mdx b/apps/docs/mintlify/changelog/changelog.mdx index 248bbb31d..760273716 100644 --- a/apps/docs/mintlify/changelog/changelog.mdx +++ b/apps/docs/mintlify/changelog/changelog.mdx @@ -4,6 +4,14 @@ mode: "center" description: "Some new things we've shipped at Autumn HQ" --- + + ## Usage alerts and `balances.limit_reached` webhook + + You can now configure **usage alerts** on customers and entities to get notified when usage crosses a threshold. Alerts are set via `billingControls.usageAlerts` and fire a `balances.usage_alert_triggered` webhook. Supports both absolute usage counts and percentage-of-allowance thresholds. + + A new **`balances.limit_reached`** webhook fires when a customer hits a usage limit — whether it's the included allowance, a max purchase cap, or a spend limit. See the [webhooks reference](/documentation/webhooks) and [spend limits & usage alerts guide](/documentation/modelling-pricing/spend-limits) for details. + + ## Express, Elysia, and Web Standard adapters for `autumn-js` diff --git a/apps/docs/mintlify/documentation/modelling-pricing/spend-limits.mdx b/apps/docs/mintlify/documentation/modelling-pricing/spend-limits.mdx index 975ef498e..83e91deb0 100644 --- a/apps/docs/mintlify/documentation/modelling-pricing/spend-limits.mdx +++ b/apps/docs/mintlify/documentation/modelling-pricing/spend-limits.mdx @@ -1,6 +1,6 @@ --- -title: Spend Limits -description: Cap overage spending per feature for customers and entities +title: Spend Limits & Usage Alerts +description: Cap overage spending and get notified when usage crosses thresholds --- Spend limits let you cap how much overage a customer (or entity) can accumulate on a usage-based feature. Without a spend limit, usage-based features allow unlimited overage — the customer is billed for whatever they use. With a spend limit, Autumn blocks usage once the overage reaches the configured cap. @@ -348,3 +348,239 @@ If you set a spend limit **higher** than the plan's max purchase, the customer w | **Dynamic** | Yes — can be changed at any time per-customer | No — applies to all customers on the plan | | **Precedence** | Overrides max purchase when set | Used as default when no spend limit is set | | **Use case** | Per-customer overage caps (e.g., enterprise spending controls) | Global safety limits for a plan tier | + +## Usage Alerts + +Usage alerts send a webhook when a customer's usage crosses a threshold you define. You can use this to take an action like sending a warning email, prompting an upgrade, or flagging the account internally. + +## Configuring usage alerts + +Usage alerts are set per-customer (or per-entity) via the API, using the same `billingControls` field as spend limits. There are two threshold types: + +- **`usage`** — fires when absolute usage reaches a specific count +- **`usage_percentage`** — fires when usage reaches a percentage of the included allowance + + + +```typescript TypeScript +import { Autumn } from "autumn-js"; + +const autumn = new Autumn({ secretKey: "am_sk_..." }); + +await autumn.customers.update({ + customerId: "user_123", + billingControls: { + usageAlerts: [{ + featureId: "api_calls", + threshold: 800, + thresholdType: "usage", + enabled: true, + name: "Approaching limit", + }], + }, +}); +``` + +```python Python +from autumn_sdk import Autumn + +autumn = Autumn("am_sk_...") + +await autumn.customers.update( + customer_id="user_123", + billing_controls={ + "usage_alerts": [{ + "feature_id": "api_calls", + "threshold": 800, + "threshold_type": "usage", + "enabled": True, + "name": "Approaching limit", + }], + }, +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/customers/update" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "billing_controls": { + "usage_alerts": [{ + "feature_id": "api_calls", + "threshold": 800, + "threshold_type": "usage", + "enabled": true, + "name": "Approaching limit" + }] + } + }' +``` + + + +For a percentage-based alert, use `threshold_type: "usage_percentage"` with a value between 0 and 100: + + + +```typescript TypeScript +await autumn.customers.update({ + customerId: "user_123", + billingControls: { + usageAlerts: [{ + featureId: "api_calls", + threshold: 80, + thresholdType: "usage_percentage", + enabled: true, + name: "80% usage warning", + }], + }, +}); +``` + +```python Python +await autumn.customers.update( + customer_id="user_123", + billing_controls={ + "usage_alerts": [{ + "feature_id": "api_calls", + "threshold": 80, + "threshold_type": "usage_percentage", + "enabled": True, + "name": "80% usage warning", + }], + }, +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/customers/update" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "billing_controls": { + "usage_alerts": [{ + "feature_id": "api_calls", + "threshold": 80, + "threshold_type": "usage_percentage", + "enabled": true, + "name": "80% usage warning" + }] + } + }' +``` + + + + +The `usage_percentage` threshold is calculated against the **included** allowance only. If the customer has overage enabled with a spend limit or max purchase, the percentage still refers to the included balance — not the total available usage. + + +### Usage alert fields + +| Field | Type | Description | +|-------|------|-------------| +| `feature_id` | string (optional) | The feature to monitor. If omitted, applies to all features. | +| `threshold` | number | The value that triggers the alert. Absolute count for `usage`, percentage (0-100) for `usage_percentage`. | +| `threshold_type` | string | `"usage"` for an absolute count, `"usage_percentage"` for a percentage of the included allowance. | +| `enabled` | boolean | Whether the alert is active. Defaults to `true`. | +| `name` | string (optional) | A label to distinguish multiple alerts on the same feature. | + +## How usage alerts work + +1. On each `track` call, Autumn compares the customer's old and new usage against each enabled alert +2. If the usage crosses the threshold (old usage was below, new usage is at or above), Autumn fires a `balances.usage_alert_triggered` webhook +3. The alert fires **once** per threshold crossing — it won't re-fire on subsequent track calls unless usage drops below the threshold and crosses it again + + +Alerts also work at the entity level. If you configure alerts on an [entity](/documentation/customers/feature-entities), they fire based on that entity's usage independently. + + +See the [balances.usage_alert_triggered webhook schema](/api-reference/webhooks/balancesUsageAlertTriggered) for the full payload reference. + +## Multiple usage alerts + +You can configure multiple alerts on the same feature or across different features. Each alert fires independently when its threshold is crossed. + + + +```typescript TypeScript +await autumn.customers.update({ + customerId: "user_123", + billingControls: { + usageAlerts: [ + { + featureId: "api_calls", + threshold: 500, + thresholdType: "usage", + enabled: true, + name: "500 calls used", + }, + { + featureId: "api_calls", + threshold: 90, + thresholdType: "usage_percentage", + enabled: true, + name: "90% allowance used", + }, + ], + }, +}); +``` + +```python Python +await autumn.customers.update( + customer_id="user_123", + billing_controls={ + "usage_alerts": [ + { + "feature_id": "api_calls", + "threshold": 500, + "threshold_type": "usage", + "enabled": True, + "name": "500 calls used", + }, + { + "feature_id": "api_calls", + "threshold": 90, + "threshold_type": "usage_percentage", + "enabled": True, + "name": "90% allowance used", + }, + ], + }, +) +``` + +```bash cURL +curl -X POST "https://api.useautumn.com/v1/customers/update" \ + -H "Authorization: Bearer am_sk_..." \ + -H "Content-Type: application/json" \ + -d '{ + "customer_id": "user_123", + "billing_controls": { + "usage_alerts": [ + { + "feature_id": "api_calls", + "threshold": 500, + "threshold_type": "usage", + "enabled": true, + "name": "500 calls used" + }, + { + "feature_id": "api_calls", + "threshold": 90, + "threshold_type": "usage_percentage", + "enabled": true, + "name": "90% allowance used" + } + ] + } + }' +``` + + + +In this example, the customer will receive two separate webhook events as they use their API calls: one when they hit 500 absolute calls, and another when they reach 90% of their included allowance. diff --git a/server/tests/integration/balances/legacy/legacy-update-balance.test.ts b/server/tests/integration/balances/legacy/legacy-update-balance.test.ts index c37518d06..8d7612ea0 100644 --- a/server/tests/integration/balances/legacy/legacy-update-balance.test.ts +++ b/server/tests/integration/balances/legacy/legacy-update-balance.test.ts @@ -22,11 +22,9 @@ test.concurrent(`${chalk.yellowBright("legacy-update-balance1: V1 API setBalance setup: [ s.customer({ testClock: true, paymentMethod: "success" }), s.products({ list: [pro] }), - s.entities({ count: 1, featureId: TestFeature.Credits }), - ], - actions: [ - s.attach({ productId: pro.id, entityIndex: 0 }), + s.entities({ count: 1, featureId: TestFeature.Workflows }), ], + actions: [s.attach({ productId: pro.id, entityIndex: 0 })], }); const entityId = entities[0].id; diff --git a/server/tests/integration/balances/lock/check-with-lock-edge-cases.test.ts b/server/tests/integration/balances/lock/check-with-lock-edge-cases.test.ts index ac449b4ae..210b80d26 100644 --- a/server/tests/integration/balances/lock/check-with-lock-edge-cases.test.ts +++ b/server/tests/integration/balances/lock/check-with-lock-edge-cases.test.ts @@ -208,6 +208,8 @@ test.concurrent(`${chalk.yellowBright("lock-edge EC-1: lock crosses monthly→li lock: { enabled: true, lock_id: lockKey }, }); + await timeout(3000); + // Verify state after check: total=90 (lifetime=90, monthly=0) const afterCheck = await autumnV2_1.customers.get(customerId); expectBalanceCorrect({ @@ -303,6 +305,8 @@ test.concurrent(`${chalk.yellowBright("lock-edge EC-2: lock crosses monthly→li lock: { enabled: true, lock_id: lockKey }, }); + await timeout(3000); + // Verify state after check: total=90 const afterCheck = await autumnV2_1.customers.get(customerId); expectBalanceCorrect({ From c8f7ae72becd0b7cf50b1821d961704211a484dd Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 23 Mar 2026 17:02:57 +0000 Subject: [PATCH 38/49] chore: added preview attach params --- .../api-reference/billing/previewAttach.mdx | 9 ++++- .../billing/previewMultiAttach.mdx | 9 ++++- apps/docs/mintlify/api/openapi.yml | 32 ++++++++++++++- bun.lock | 2 +- others/python-sdk/.speakeasy/gen.lock | 40 +++++++++++-------- .../src/autumn_sdk/models/__init__.py | 6 +++ .../src/autumn_sdk/models/previewattachop.py | 33 +++++++++++++-- .../autumn_sdk/models/previewmultiattachop.py | 33 +++++++++++++-- .../src/generated/previewAttachSchemas.ts | 6 +++ .../generated/previewMultiAttachSchemas.ts | 6 +++ packages/openapi/openapi-stripped.yml | 32 ++++++++++++++- packages/openapi/openapi.yml | 32 ++++++++++++++- packages/sdk/.speakeasy/gen.lock | 36 ++++++++++------- packages/sdk/.speakeasy/out.openapi.yaml | 28 ++++++++++++- packages/sdk/.speakeasy/workflow.lock | 8 ++-- packages/sdk/src/models/preview-attach-op.ts | 32 +++++++++++++-- .../sdk/src/models/preview-multi-attach-op.ts | 32 +++++++++++++-- .../billing/common/attachPreviewResponse.ts | 8 +--- 18 files changed, 319 insertions(+), 65 deletions(-) diff --git a/apps/docs/mintlify/api-reference/billing/previewAttach.mdx b/apps/docs/mintlify/api-reference/billing/previewAttach.mdx index 45c72fc71..42939faa3 100644 --- a/apps/docs/mintlify/api-reference/billing/previewAttach.mdx +++ b/apps/docs/mintlify/api-reference/billing/previewAttach.mdx @@ -307,7 +307,6 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx - List of line items for the current billing period. The name of the line item to display to the customer if you're building a UI. It will either be the plan name or the feature name. @@ -1118,6 +1117,14 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + Whether the customer will be redirected to a checkout page if attach is called. + + + + The type of checkout that will be used if the customer is redirected to a checkout page. + + ```json 200 diff --git a/apps/docs/mintlify/api-reference/billing/previewMultiAttach.mdx b/apps/docs/mintlify/api-reference/billing/previewMultiAttach.mdx index 378b81392..0289b2800 100644 --- a/apps/docs/mintlify/api-reference/billing/previewMultiAttach.mdx +++ b/apps/docs/mintlify/api-reference/billing/previewMultiAttach.mdx @@ -450,7 +450,6 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx - List of line items for the current billing period. The name of the line item to display to the customer if you're building a UI. It will either be the plan name or the feature name. @@ -1261,6 +1260,14 @@ import { DynamicResponseExample } from "/components/dynamic-response-example.jsx + + Whether the customer will be redirected to a checkout page if attach is called. + + + + The type of checkout that will be used if the customer is redirected to a checkout page. + + ```json 200 diff --git a/apps/docs/mintlify/api/openapi.yml b/apps/docs/mintlify/api/openapi.yml index 16dae50d0..5096f8e38 100644 --- a/apps/docs/mintlify/api/openapi.yml +++ b/apps/docs/mintlify/api/openapi.yml @@ -7978,7 +7978,6 @@ paths: - plan_id - feature_id - quantity - description: List of line items for the current billing period. subtotal: type: number description: The total amount in cents before discounts for the current billing @@ -8222,6 +8221,19 @@ paths: - canceled_at - expires_at description: Products or subscription changes being removed or ended. + redirect_to_checkout: + type: boolean + description: Whether the customer will be redirected to a checkout page if + attach is called. + checkout_type: + anyOf: + - enum: + - stripe_checkout + - autumn_checkout + type: string + - type: "null" + description: The type of checkout that will be used if the customer is + redirected to a checkout page. required: - customer_id - line_items @@ -8230,6 +8242,8 @@ paths: - currency - incoming - outgoing + - redirect_to_checkout + - checkout_type examples: - &a31 customerId: charles @@ -8776,7 +8790,6 @@ paths: - plan_id - feature_id - quantity - description: List of line items for the current billing period. subtotal: type: number description: The total amount in cents before discounts for the current billing @@ -9020,6 +9033,19 @@ paths: - canceled_at - expires_at description: Products or subscription changes being removed or ended. + redirect_to_checkout: + type: boolean + description: Whether the customer will be redirected to a checkout page if + attach is called. + checkout_type: + anyOf: + - enum: + - stripe_checkout + - autumn_checkout + type: string + - type: "null" + description: The type of checkout that will be used if the customer is + redirected to a checkout page. required: - customer_id - line_items @@ -9028,6 +9054,8 @@ paths: - currency - incoming - outgoing + - redirect_to_checkout + - checkout_type examples: - &a33 customerId: charles diff --git a/bun.lock b/bun.lock index 2c6fccad6..7bc5a7a9c 100644 --- a/bun.lock +++ b/bun.lock @@ -175,7 +175,7 @@ }, "packages/autumn-js": { "name": "autumn-js", - "version": "1.0.7", + "version": "1.1.0", "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", diff --git a/others/python-sdk/.speakeasy/gen.lock b/others/python-sdk/.speakeasy/gen.lock index abdfa8845..6981324e8 100644 --- a/others/python-sdk/.speakeasy/gen.lock +++ b/others/python-sdk/.speakeasy/gen.lock @@ -1,16 +1,16 @@ lockVersion: 2.0.0 id: 05940b80-1ef8-40f4-9878-822fb2792070 management: - docChecksum: b2a039ee0808d119f6be0c0d1504525b + docChecksum: fef7a15b8ff1dae40572330e3d3fc94e docVersion: 2.2.0 speakeasyVersion: 1.719.0 generationVersion: 2.824.1 releaseVersion: 0.4.18 configChecksum: 2263d20254e354a1792248274002f650 persistentEdits: - generation_id: cf955dd6-87ca-4610-b2e5-d305fef5523b - pristine_commit_hash: 192f7483eda75fc42bfeeb9b13aa9f5790ef8607 - pristine_tree_hash: a466b4e81e20d68c76a1721c9ade3187c3fedbb8 + generation_id: 7f943529-cf04-4913-a930-5fabb845bed5 + pristine_commit_hash: 78a022ad50904f09c57f220bbaefb1e17e34733e + pristine_tree_hash: 8f92eb8f4a08f2969fa5dfcdab5fe8f57b807ab7 features: python: additionalDependencies: 1.0.0 @@ -1751,6 +1751,10 @@ trackedFiles: id: d3b9f209493b last_write_checksum: sha1:ce34a405f234b04f41fd095c815f298dd4ee0fa8 pristine_git_object: 0d3745bf063a16a8ad4566cb989fd8cdb49415cf + docs/models/previewattachcheckouttype.md: + id: 7e32709f1445 + last_write_checksum: sha1:c2190a0c12f41d6782bcc7b0866bcc9d9aaaef30 + pristine_git_object: 95bb8a9e3f2191e805adf28b5d8513594353feae docs/models/previewattachcustomize.md: id: dd921922e55d last_write_checksum: sha1:9c19dbab93cf326a50e7d1d9a0d50bd8df9063ca @@ -1881,8 +1885,8 @@ trackedFiles: pristine_git_object: 3e9b1be359f8832c3439cad7e04566bb40ceaed1 docs/models/previewattachresponse.md: id: a678e8dbf94c - last_write_checksum: sha1:e9e75a12ec734c69bd71489f74ee3ffe501c5a04 - pristine_git_object: 589d180a5ae4bdc58d3c7eeda00368fc6a9230bd + last_write_checksum: sha1:1e8f007a03dad171fbb08118a6adb2d9e8f9fc74 + pristine_git_object: 3925e01875b4e6c9eca9258adc84392ad595f7ec docs/models/previewattachrollover.md: id: 8ef3a1be0fed last_write_checksum: sha1:d2cf512026309abaf59ee66a3fa2150db0b7fde9 @@ -1923,6 +1927,10 @@ trackedFiles: id: c92637a0950c last_write_checksum: sha1:b3b600448b1f704d40dab1df259d606c3e44ab5e pristine_git_object: 8bb4488808e3af817c5331cebfd0fbc66fbc4323 + docs/models/previewmultiattachcheckouttype.md: + id: 978368a6ba5e + last_write_checksum: sha1:6b63a52d14cc7ae065c2cf008dc231cfcc798520 + pristine_git_object: 84d5019b44a86b87e5efa8f7981c22a62686df66 docs/models/previewmultiattachcustomize.md: id: e2e0b7d3e939 last_write_checksum: sha1:31e8437aa83882d1f206b0203cdf25a7b37932f1 @@ -2049,8 +2057,8 @@ trackedFiles: pristine_git_object: ec56bdecfc8eac78fffde4302cee7caed963686a docs/models/previewmultiattachresponse.md: id: 122c343c9098 - last_write_checksum: sha1:24a707473c614f9dc2140437bf133f0ca909d155 - pristine_git_object: bdc7802a637a993e771664d0ff3f3a5f552c2dc9 + last_write_checksum: sha1:98c7bf93d60e58016f7d6a167915e2d17f853a98 + pristine_git_object: 02bf98cf18cae23d0b02bcfcbfc57ca4d8043af9 docs/models/previewmultiattachrollover.md: id: 156269f00e06 last_write_checksum: sha1:d757d222ff8014fa24d40928eb47984131ca6c76 @@ -3013,8 +3021,8 @@ trackedFiles: pristine_git_object: 89560b566073785535643e694c112bedbd3db13d src/autumn_sdk/models/__init__.py: id: bcf3802243ff - last_write_checksum: sha1:ff258b03469e08db7b70dd3c780b34cad56667e4 - pristine_git_object: b397b6b41232d533068c68ad67c5e4f3b30ccded + last_write_checksum: sha1:f7e204fae5a79d0e0810a82b8ac9d0ef4efc6da5 + pristine_git_object: e06b4ee89b30d9f04782e65df02b13d890053a07 src/autumn_sdk/models/aggregateeventsop.py: id: 01321099f2a5 last_write_checksum: sha1:eef6ab478132141f11c820ee80cbacaa4bc74870 @@ -3141,12 +3149,12 @@ trackedFiles: pristine_git_object: 56a42a2d22eb3a21547ee87870f73f7c247aefa3 src/autumn_sdk/models/previewattachop.py: id: 2b361be4bfa8 - last_write_checksum: sha1:5bb1e64fd73f058a43b900c665809c0dc952cb27 - pristine_git_object: dacc7ba980a8f9701af6f1534227cfea36765d44 + last_write_checksum: sha1:1bdfc9ccebe681fe768091a3a9c89b17b1024b14 + pristine_git_object: 36fa9531a2eb8bd27b2c4f45642407addbc71db0 src/autumn_sdk/models/previewmultiattachop.py: id: 963ffcd646a4 - last_write_checksum: sha1:a4f24fcc5db4c927f48a32688652117a890ea17f - pristine_git_object: 06df124f54e5a536317aeda045079cbb83a79534 + last_write_checksum: sha1:aa6c7b216e120a4175f8d13cf45ca2e6d5551fdc + pristine_git_object: 5056bbba15475abd5b1caa40aca74c0c3a239d22 src/autumn_sdk/models/previewupdateop.py: id: 081d5f08508d last_write_checksum: sha1:c526e015b5085720840a5e38e2c8e41968ed9f16 @@ -3461,7 +3469,7 @@ examples: application/json: {"customer_id": "cus_123", "plan_id": "pro_plan", "redirect_mode": "if_required"} responses: "200": - application/json: {"customer_id": "", "line_items": [], "subtotal": 20, "total": 20, "currency": "usd", "incoming": [], "outgoing": []} + application/json: {"customer_id": "", "line_items": [], "subtotal": 20, "total": 20, "currency": "usd", "incoming": [], "outgoing": [], "redirect_to_checkout": true, "checkout_type": "stripe_checkout"} updateSubscription: speakeasy-default-update-subscription: parameters: @@ -3769,7 +3777,7 @@ examples: application/json: {"customer_id": "cus_123", "plans": [{"plan_id": "pro_plan"}, {"plan_id": "addon_seats", "feature_quantities": [{"feature_id": "seats", "quantity": 5}]}], "redirect_mode": "if_required"} responses: "200": - application/json: {"customer_id": "", "line_items": [{"display_name": "Percival_Towne81", "description": "as unfortunately wherever crest", "subtotal": 1273.44, "total": 7630.04, "plan_id": "", "feature_id": null, "quantity": 1062.24}], "subtotal": 20, "total": 20, "currency": "usd", "incoming": [{"plan_id": "", "feature_quantities": [], "effective_at": 1983.1, "canceled_at": 2257.08, "expires_at": 7910.67}], "outgoing": []} + application/json: {"customer_id": "", "line_items": [{"display_name": "Percival_Towne81", "description": "as unfortunately wherever crest", "subtotal": 1273.44, "total": 7630.04, "plan_id": "", "feature_id": null, "quantity": 1062.24}], "subtotal": 20, "total": 20, "currency": "usd", "incoming": [{"plan_id": "", "feature_quantities": [], "effective_at": 1983.1, "canceled_at": 2257.08, "expires_at": 7910.67}], "outgoing": [], "redirect_to_checkout": false, "checkout_type": "autumn_checkout"} multiAttach: speakeasy-default-multi-attach: parameters: diff --git a/others/python-sdk/src/autumn_sdk/models/__init__.py b/others/python-sdk/src/autumn_sdk/models/__init__.py index b397b6b41..e06b4ee89 100644 --- a/others/python-sdk/src/autumn_sdk/models/__init__.py +++ b/others/python-sdk/src/autumn_sdk/models/__init__.py @@ -804,6 +804,7 @@ PreviewAttachCarryOverBalancesTypedDict, PreviewAttachCarryOverUsages, PreviewAttachCarryOverUsagesTypedDict, + PreviewAttachCheckoutType, PreviewAttachCustomLineItem, PreviewAttachCustomLineItemTypedDict, PreviewAttachCustomize, @@ -880,6 +881,7 @@ PreviewMultiAttachBillingControls, PreviewMultiAttachBillingControlsTypedDict, PreviewMultiAttachBillingMethod, + PreviewMultiAttachCheckoutType, PreviewMultiAttachCustomize, PreviewMultiAttachCustomizeTypedDict, PreviewMultiAttachDiscount, @@ -1989,6 +1991,7 @@ "PreviewAttachCarryOverBalancesTypedDict", "PreviewAttachCarryOverUsages", "PreviewAttachCarryOverUsagesTypedDict", + "PreviewAttachCheckoutType", "PreviewAttachCustomLineItem", "PreviewAttachCustomLineItemTypedDict", "PreviewAttachCustomize", @@ -2063,6 +2066,7 @@ "PreviewMultiAttachBillingControls", "PreviewMultiAttachBillingControlsTypedDict", "PreviewMultiAttachBillingMethod", + "PreviewMultiAttachCheckoutType", "PreviewMultiAttachCustomize", "PreviewMultiAttachCustomizeTypedDict", "PreviewMultiAttachDiscount", @@ -3211,6 +3215,7 @@ "PreviewAttachCarryOverBalancesTypedDict": ".previewattachop", "PreviewAttachCarryOverUsages": ".previewattachop", "PreviewAttachCarryOverUsagesTypedDict": ".previewattachop", + "PreviewAttachCheckoutType": ".previewattachop", "PreviewAttachCustomLineItem": ".previewattachop", "PreviewAttachCustomLineItemTypedDict": ".previewattachop", "PreviewAttachCustomize": ".previewattachop", @@ -3285,6 +3290,7 @@ "PreviewMultiAttachBillingControls": ".previewmultiattachop", "PreviewMultiAttachBillingControlsTypedDict": ".previewmultiattachop", "PreviewMultiAttachBillingMethod": ".previewmultiattachop", + "PreviewMultiAttachCheckoutType": ".previewmultiattachop", "PreviewMultiAttachCustomize": ".previewmultiattachop", "PreviewMultiAttachCustomizeTypedDict": ".previewmultiattachop", "PreviewMultiAttachDiscount": ".previewmultiattachop", diff --git a/others/python-sdk/src/autumn_sdk/models/previewattachop.py b/others/python-sdk/src/autumn_sdk/models/previewattachop.py index dacc7ba98..36fa9531a 100644 --- a/others/python-sdk/src/autumn_sdk/models/previewattachop.py +++ b/others/python-sdk/src/autumn_sdk/models/previewattachop.py @@ -8,6 +8,7 @@ OptionalNullable, UNSET, UNSET_SENTINEL, + UnrecognizedStr, ) from autumn_sdk.utils import FieldMetadata, HeaderMetadata import pydantic @@ -1381,13 +1382,21 @@ def serialize_model(self, handler): return m +PreviewAttachCheckoutType = Union[ + Literal[ + "stripe_checkout", + "autumn_checkout", + ], + UnrecognizedStr, +] + + class PreviewAttachResponseTypedDict(TypedDict): r"""OK""" customer_id: str r"""The ID of the customer.""" line_items: List[PreviewAttachLineItemTypedDict] - r"""List of line items for the current billing period.""" subtotal: float r"""The total amount in cents before discounts for the current billing period.""" total: float @@ -1398,6 +1407,10 @@ class PreviewAttachResponseTypedDict(TypedDict): r"""Products or subscription changes being added or updated.""" outgoing: List[PreviewAttachOutgoingTypedDict] r"""Products or subscription changes being removed or ended.""" + redirect_to_checkout: bool + r"""Whether the customer will be redirected to a checkout page if attach is called.""" + checkout_type: Nullable[PreviewAttachCheckoutType] + r"""The type of checkout that will be used if the customer is redirected to a checkout page.""" next_cycle: NotRequired[PreviewAttachNextCycleTypedDict] r"""Preview of the next billing cycle, if applicable. This shows what the customer will be charged in subsequent cycles.""" expand: NotRequired[List[str]] @@ -1411,7 +1424,6 @@ class PreviewAttachResponse(BaseModel): r"""The ID of the customer.""" line_items: List[PreviewAttachLineItem] - r"""List of line items for the current billing period.""" subtotal: float r"""The total amount in cents before discounts for the current billing period.""" @@ -1428,6 +1440,12 @@ class PreviewAttachResponse(BaseModel): outgoing: List[PreviewAttachOutgoing] r"""Products or subscription changes being removed or ended.""" + redirect_to_checkout: bool + r"""Whether the customer will be redirected to a checkout page if attach is called.""" + + checkout_type: Nullable[PreviewAttachCheckoutType] + r"""The type of checkout that will be used if the customer is redirected to a checkout page.""" + next_cycle: Optional[PreviewAttachNextCycle] = None r"""Preview of the next billing cycle, if applicable. This shows what the customer will be charged in subsequent cycles.""" @@ -1437,15 +1455,24 @@ class PreviewAttachResponse(BaseModel): @model_serializer(mode="wrap") def serialize_model(self, handler): optional_fields = set(["next_cycle", "expand"]) + nullable_fields = set(["checkout_type"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n val = serialized.get(k) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) if val != UNSET_SENTINEL: - if val is not None or k not in optional_fields: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): m[k] = val return m diff --git a/others/python-sdk/src/autumn_sdk/models/previewmultiattachop.py b/others/python-sdk/src/autumn_sdk/models/previewmultiattachop.py index 06df124f5..5056bbba1 100644 --- a/others/python-sdk/src/autumn_sdk/models/previewmultiattachop.py +++ b/others/python-sdk/src/autumn_sdk/models/previewmultiattachop.py @@ -9,6 +9,7 @@ OptionalNullable, UNSET, UNSET_SENTINEL, + UnrecognizedStr, ) from autumn_sdk.utils import FieldMetadata, HeaderMetadata import pydantic @@ -1455,13 +1456,21 @@ def serialize_model(self, handler): return m +PreviewMultiAttachCheckoutType = Union[ + Literal[ + "stripe_checkout", + "autumn_checkout", + ], + UnrecognizedStr, +] + + class PreviewMultiAttachResponseTypedDict(TypedDict): r"""OK""" customer_id: str r"""The ID of the customer.""" line_items: List[PreviewMultiAttachLineItemTypedDict] - r"""List of line items for the current billing period.""" subtotal: float r"""The total amount in cents before discounts for the current billing period.""" total: float @@ -1472,6 +1481,10 @@ class PreviewMultiAttachResponseTypedDict(TypedDict): r"""Products or subscription changes being added or updated.""" outgoing: List[PreviewMultiAttachOutgoingTypedDict] r"""Products or subscription changes being removed or ended.""" + redirect_to_checkout: bool + r"""Whether the customer will be redirected to a checkout page if attach is called.""" + checkout_type: Nullable[PreviewMultiAttachCheckoutType] + r"""The type of checkout that will be used if the customer is redirected to a checkout page.""" next_cycle: NotRequired[PreviewMultiAttachNextCycleTypedDict] r"""Preview of the next billing cycle, if applicable. This shows what the customer will be charged in subsequent cycles.""" expand: NotRequired[List[str]] @@ -1485,7 +1498,6 @@ class PreviewMultiAttachResponse(BaseModel): r"""The ID of the customer.""" line_items: List[PreviewMultiAttachLineItem] - r"""List of line items for the current billing period.""" subtotal: float r"""The total amount in cents before discounts for the current billing period.""" @@ -1502,6 +1514,12 @@ class PreviewMultiAttachResponse(BaseModel): outgoing: List[PreviewMultiAttachOutgoing] r"""Products or subscription changes being removed or ended.""" + redirect_to_checkout: bool + r"""Whether the customer will be redirected to a checkout page if attach is called.""" + + checkout_type: Nullable[PreviewMultiAttachCheckoutType] + r"""The type of checkout that will be used if the customer is redirected to a checkout page.""" + next_cycle: Optional[PreviewMultiAttachNextCycle] = None r"""Preview of the next billing cycle, if applicable. This shows what the customer will be charged in subsequent cycles.""" @@ -1511,15 +1529,24 @@ class PreviewMultiAttachResponse(BaseModel): @model_serializer(mode="wrap") def serialize_model(self, handler): optional_fields = set(["next_cycle", "expand"]) + nullable_fields = set(["checkout_type"]) serialized = handler(self) m = {} for n, f in type(self).model_fields.items(): k = f.alias or n val = serialized.get(k) + is_nullable_and_explicitly_set = ( + k in nullable_fields + and (self.__pydantic_fields_set__.intersection({n})) # pylint: disable=no-member + ) if val != UNSET_SENTINEL: - if val is not None or k not in optional_fields: + if ( + val is not None + or k not in optional_fields + or is_nullable_and_explicitly_set + ): m[k] = val return m diff --git a/packages/autumn-js/src/generated/previewAttachSchemas.ts b/packages/autumn-js/src/generated/previewAttachSchemas.ts index 8d47c706a..c39d61322 100644 --- a/packages/autumn-js/src/generated/previewAttachSchemas.ts +++ b/packages/autumn-js/src/generated/previewAttachSchemas.ts @@ -287,6 +287,8 @@ const closedEnumSchema = z.any(); const planSchema = z.any(); +const openEnumSchema = z.any(); + export const previewAttachPriceIntervalSchema = closedEnumSchema; export const previewAttachBasePriceSchema = z.object({ @@ -438,6 +440,8 @@ export const previewAttachOutgoingSchema = z.object({ expiresAt: z.number().nullable(), }); +export const previewAttachCheckoutTypeSchema = openEnumSchema; + export const previewAttachResponseSchema = z.object({ customerId: z.string(), lineItems: z.array(previewAttachLineItemSchema), @@ -448,4 +452,6 @@ export const previewAttachResponseSchema = z.object({ expand: z.union([z.array(z.string()), z.undefined()]).optional(), incoming: z.array(previewAttachIncomingSchema), outgoing: z.array(previewAttachOutgoingSchema), + redirectToCheckout: z.boolean(), + checkoutType: previewAttachCheckoutTypeSchema.nullable(), }); diff --git a/packages/autumn-js/src/generated/previewMultiAttachSchemas.ts b/packages/autumn-js/src/generated/previewMultiAttachSchemas.ts index 72c523cea..d2ee053f6 100644 --- a/packages/autumn-js/src/generated/previewMultiAttachSchemas.ts +++ b/packages/autumn-js/src/generated/previewMultiAttachSchemas.ts @@ -273,6 +273,8 @@ const customerDataSchema = z.any(); const planSchema = z.any(); +const openEnumSchema = z.any(); + const customerDataOutboundSchema = z.any(); export const previewMultiAttachPriceIntervalSchema = closedEnumSchema; @@ -452,6 +454,8 @@ export const previewMultiAttachOutgoingSchema = z.object({ expiresAt: z.number().nullable(), }); +export const previewMultiAttachCheckoutTypeSchema = openEnumSchema; + export const previewMultiAttachResponseSchema = z.object({ customerId: z.string(), lineItems: z.array(previewMultiAttachLineItemSchema), @@ -464,6 +468,8 @@ export const previewMultiAttachResponseSchema = z.object({ expand: z.union([z.array(z.string()), z.undefined()]).optional(), incoming: z.array(previewMultiAttachIncomingSchema), outgoing: z.array(previewMultiAttachOutgoingSchema), + redirectToCheckout: z.boolean(), + checkoutType: previewMultiAttachCheckoutTypeSchema.nullable(), }); export const previewMultiAttachParamsOutboundSchema = z.object({ diff --git a/packages/openapi/openapi-stripped.yml b/packages/openapi/openapi-stripped.yml index 131c6dde6..999e5734e 100644 --- a/packages/openapi/openapi-stripped.yml +++ b/packages/openapi/openapi-stripped.yml @@ -7487,7 +7487,6 @@ paths: - plan_id - feature_id - quantity - description: List of line items for the current billing period. subtotal: type: number description: The total amount in cents before discounts for the current billing @@ -7731,6 +7730,19 @@ paths: - canceled_at - expires_at description: Products or subscription changes being removed or ended. + redirect_to_checkout: + type: boolean + description: Whether the customer will be redirected to a checkout page if + attach is called. + checkout_type: + anyOf: + - enum: + - stripe_checkout + - autumn_checkout + type: string + - type: "null" + description: The type of checkout that will be used if the customer is + redirected to a checkout page. required: - customer_id - line_items @@ -7739,6 +7751,8 @@ paths: - currency - incoming - outgoing + - redirect_to_checkout + - checkout_type examples: - &a32 customerId: charles @@ -8255,7 +8269,6 @@ paths: - plan_id - feature_id - quantity - description: List of line items for the current billing period. subtotal: type: number description: The total amount in cents before discounts for the current billing @@ -8499,6 +8512,19 @@ paths: - canceled_at - expires_at description: Products or subscription changes being removed or ended. + redirect_to_checkout: + type: boolean + description: Whether the customer will be redirected to a checkout page if + attach is called. + checkout_type: + anyOf: + - enum: + - stripe_checkout + - autumn_checkout + type: string + - type: "null" + description: The type of checkout that will be used if the customer is + redirected to a checkout page. required: - customer_id - line_items @@ -8507,6 +8533,8 @@ paths: - currency - incoming - outgoing + - redirect_to_checkout + - checkout_type examples: - &a34 customerId: charles diff --git a/packages/openapi/openapi.yml b/packages/openapi/openapi.yml index 3ffd44946..94fd2dd3b 100644 --- a/packages/openapi/openapi.yml +++ b/packages/openapi/openapi.yml @@ -8002,7 +8002,6 @@ paths: - plan_id - feature_id - quantity - description: List of line items for the current billing period. subtotal: type: number description: The total amount in cents before discounts for the current billing @@ -8246,6 +8245,19 @@ paths: - canceled_at - expires_at description: Products or subscription changes being removed or ended. + redirect_to_checkout: + type: boolean + description: Whether the customer will be redirected to a checkout page if + attach is called. + checkout_type: + anyOf: + - enum: + - stripe_checkout + - autumn_checkout + type: string + - type: "null" + description: The type of checkout that will be used if the customer is + redirected to a checkout page. required: - customer_id - line_items @@ -8254,6 +8266,8 @@ paths: - currency - incoming - outgoing + - redirect_to_checkout + - checkout_type examples: - customerId: charles lineItems: @@ -8782,7 +8796,6 @@ paths: - plan_id - feature_id - quantity - description: List of line items for the current billing period. subtotal: type: number description: The total amount in cents before discounts for the current billing @@ -9026,6 +9039,19 @@ paths: - canceled_at - expires_at description: Products or subscription changes being removed or ended. + redirect_to_checkout: + type: boolean + description: Whether the customer will be redirected to a checkout page if + attach is called. + checkout_type: + anyOf: + - enum: + - stripe_checkout + - autumn_checkout + type: string + - type: "null" + description: The type of checkout that will be used if the customer is + redirected to a checkout page. required: - customer_id - line_items @@ -9034,6 +9060,8 @@ paths: - currency - incoming - outgoing + - redirect_to_checkout + - checkout_type examples: - customerId: charles lineItems: diff --git a/packages/sdk/.speakeasy/gen.lock b/packages/sdk/.speakeasy/gen.lock index dd20656ce..b15acafdf 100644 --- a/packages/sdk/.speakeasy/gen.lock +++ b/packages/sdk/.speakeasy/gen.lock @@ -1,16 +1,16 @@ lockVersion: 2.0.0 id: 7b300647-cd76-49e9-bf77-7d1bf5446d66 management: - docChecksum: a6df9b64f035c4af93b077b3174ecba8 + docChecksum: 78d7356dae7a34b2378e4b07fe000796 docVersion: 2.2.0 speakeasyVersion: 1.719.0 generationVersion: 2.824.1 releaseVersion: 0.10.17 configChecksum: c6b2bd1231da8dc3af5be7430f3cfbac persistentEdits: - generation_id: f036c341-af7f-424d-b5c5-30b3e9b172a1 - pristine_commit_hash: 5127dc545cc26086977773862cd85d97ea348a80 - pristine_tree_hash: 6345cc3cfa59bf954c97f04795550fd94caa4e6d + generation_id: b7ea8937-0e4f-4826-a433-1eedb364cae8 + pristine_commit_hash: 23d40e77b5706bab1123fe898d70572d17cb40d6 + pristine_tree_hash: ccd0836cfa89e67dc25ee5e6fcc746e8c4236532 features: typescript: additionalDependencies: 0.1.0 @@ -1766,6 +1766,10 @@ trackedFiles: id: 0d4fd69ce60a last_write_checksum: sha1:faacda974a568d152feba301999681b0ac9b9f22 pristine_git_object: 89e5a8a27f416bac9b61b262130d80b433af30fa + docs/models/preview-attach-checkout-type.md: + id: e07d719a04f8 + last_write_checksum: sha1:5de215a1a421118b1692a55427c29995d89a258a + pristine_git_object: 373a83c392da04e01d876533f4888962a210e293 docs/models/preview-attach-custom-line-item.md: id: 25b7ef90afa8 last_write_checksum: sha1:29214774ba3581b151effe5b1fa3a06cdfd65bab @@ -1896,8 +1900,8 @@ trackedFiles: pristine_git_object: 38738348cb8b3cb3f1466f4628e7c049ac8bc0a9 docs/models/preview-attach-response.md: id: f53481e3c4e9 - last_write_checksum: sha1:f2597a87350bc64b10ef0b87ed88bfae05cbc107 - pristine_git_object: c6f5e8ee502064aa1152e9d66694021bc13aae49 + last_write_checksum: sha1:2321d1daa9a9798381b019e473cb5111439f0a57 + pristine_git_object: 1f6736446a7e452e9f945e38eab00eb02c6ebb10 docs/models/preview-attach-rollover.md: id: 45bd31144856 last_write_checksum: sha1:3a2f09b2565a522e050a728c885e822640ba0793 @@ -1938,6 +1942,10 @@ trackedFiles: id: 08a032b6436d last_write_checksum: sha1:8d37dcbdb16b45192fd041922ebd564c88e67b22 pristine_git_object: b5ac162f3668add1fb4436cc393f459c5da0b89f + docs/models/preview-multi-attach-checkout-type.md: + id: b3076c317fa2 + last_write_checksum: sha1:05ef0c7754e7c943d912742fb617ad543220df7e + pristine_git_object: 138be678a4610897cff210e90d521116d12da4a7 docs/models/preview-multi-attach-customize.md: id: 24a33dc900ca last_write_checksum: sha1:b8a78a9a85c5b32cb395bd3550da68e3eed74ce4 @@ -2064,8 +2072,8 @@ trackedFiles: pristine_git_object: 76b0be7dd41f6656bd5af272c91fb99dea4f73b8 docs/models/preview-multi-attach-response.md: id: 9818ab87cd64 - last_write_checksum: sha1:e5f640fb8ec52f3b19690556f6b57ffda222a80f - pristine_git_object: 56821208a4bedf8b51437ea6c37a471679b504e9 + last_write_checksum: sha1:7b28a6d6a6caf137144098bf12c9d87df1ca0001 + pristine_git_object: cf0e178f9ecd82742002084985f524fabea80b92 docs/models/preview-multi-attach-rollover.md: id: 7d596d458a6d last_write_checksum: sha1:45d89abe23cd4d317e88ba71370ad5dd8c0ea37c @@ -3328,12 +3336,12 @@ trackedFiles: pristine_git_object: 57c2119234cc30673a067a26c8ccca1244b2295f src/models/preview-attach-op.ts: id: 3efc6e3443a7 - last_write_checksum: sha1:593828c02419a43568b504304fd85ef55c2a5843 - pristine_git_object: 69b603db9c6b7dcafcc1431820c3cc09bd104313 + last_write_checksum: sha1:00508a67173212ea0d5b15da08b16432dcbd6bdb + pristine_git_object: 397445fae8ba834df94dc6b64a150d5cf3e2e8d4 src/models/preview-multi-attach-op.ts: id: e4847dc281a6 - last_write_checksum: sha1:b81fa93c90e11a02ee71acda6542c262cca0d5c2 - pristine_git_object: 16920a58e0f56d9b268851b14404aade061dfcf3 + last_write_checksum: sha1:83f3b92521c22c106d11c6d8b42cbba6fcad6cac + pristine_git_object: 0867cc377d019d54979f8389dd3d078dafff9682 src/models/preview-update-op.ts: id: fcbbbf3b22ac last_write_checksum: sha1:76069c26da18000a06ea93d18c265637ed2c87a9 @@ -3919,7 +3927,7 @@ examples: application/json: {"customer_id": "cus_123", "plan_id": "pro_plan", "redirect_mode": "if_required"} responses: "200": - application/json: {"customer_id": "", "line_items": [], "subtotal": 20, "total": 20, "currency": "usd", "incoming": [], "outgoing": []} + application/json: {"customer_id": "", "line_items": [], "subtotal": 20, "total": 20, "currency": "usd", "incoming": [], "outgoing": [], "redirect_to_checkout": true, "checkout_type": "stripe_checkout"} updateSubscription: speakeasy-default-update-subscription: parameters: @@ -4227,7 +4235,7 @@ examples: application/json: {"customer_id": "cus_123", "plans": [{"plan_id": "pro_plan"}, {"plan_id": "addon_seats", "feature_quantities": [{"feature_id": "seats", "quantity": 5}]}], "redirect_mode": "if_required"} responses: "200": - application/json: {"customer_id": "", "line_items": [{"display_name": "Percival_Towne81", "description": "as unfortunately wherever crest", "subtotal": 1273.44, "total": 7630.04, "plan_id": "", "feature_id": null, "quantity": 1062.24}], "subtotal": 20, "total": 20, "currency": "usd", "incoming": [{"plan_id": "", "feature_quantities": [], "effective_at": 1983.1, "canceled_at": 2257.08, "expires_at": 7910.67}], "outgoing": []} + application/json: {"customer_id": "", "line_items": [{"display_name": "Percival_Towne81", "description": "as unfortunately wherever crest", "subtotal": 1273.44, "total": 7630.04, "plan_id": "", "feature_id": null, "quantity": 1062.24}], "subtotal": 20, "total": 20, "currency": "usd", "incoming": [{"plan_id": "", "feature_quantities": [], "effective_at": 1983.1, "canceled_at": 2257.08, "expires_at": 7910.67}], "outgoing": [], "redirect_to_checkout": false, "checkout_type": "autumn_checkout"} multiAttach: speakeasy-default-multi-attach: parameters: diff --git a/packages/sdk/.speakeasy/out.openapi.yaml b/packages/sdk/.speakeasy/out.openapi.yaml index 3c55826f5..95a68ca95 100644 --- a/packages/sdk/.speakeasy/out.openapi.yaml +++ b/packages/sdk/.speakeasy/out.openapi.yaml @@ -7379,7 +7379,6 @@ paths: - plan_id - feature_id - quantity - description: List of line items for the current billing period. subtotal: type: number description: The total amount in cents before discounts for the current billing period. @@ -7610,6 +7609,17 @@ paths: - canceled_at - expires_at description: Products or subscription changes being removed or ended. + redirect_to_checkout: + type: boolean + description: Whether the customer will be redirected to a checkout page if attach is called. + checkout_type: + anyOf: + - enum: + - stripe_checkout + - autumn_checkout + type: string + - type: "null" + description: The type of checkout that will be used if the customer is redirected to a checkout page. required: - customer_id - line_items @@ -7618,6 +7628,8 @@ paths: - currency - incoming - outgoing + - redirect_to_checkout + - checkout_type examples: - customerId: charles lineItems: @@ -8111,7 +8123,6 @@ paths: - plan_id - feature_id - quantity - description: List of line items for the current billing period. subtotal: type: number description: The total amount in cents before discounts for the current billing period. @@ -8342,6 +8353,17 @@ paths: - canceled_at - expires_at description: Products or subscription changes being removed or ended. + redirect_to_checkout: + type: boolean + description: Whether the customer will be redirected to a checkout page if attach is called. + checkout_type: + anyOf: + - enum: + - stripe_checkout + - autumn_checkout + type: string + - type: "null" + description: The type of checkout that will be used if the customer is redirected to a checkout page. required: - customer_id - line_items @@ -8350,6 +8372,8 @@ paths: - currency - incoming - outgoing + - redirect_to_checkout + - checkout_type examples: - customerId: charles lineItems: diff --git a/packages/sdk/.speakeasy/workflow.lock b/packages/sdk/.speakeasy/workflow.lock index d19d9afe2..2c124c01a 100644 --- a/packages/sdk/.speakeasy/workflow.lock +++ b/packages/sdk/.speakeasy/workflow.lock @@ -9,8 +9,8 @@ sources: - 2.2.0 Autumn API Stripped: sourceNamespace: autumn-api-stripped - sourceRevisionDigest: sha256:73b97e1e3131f4a268daddbc9107f4e77beaf628f9308078516cb84848a94480 - sourceBlobDigest: sha256:125b8a9f6111ceb84655b9f7eae781f5a566c8946bee87632a501e382f0a06db + sourceRevisionDigest: sha256:07eb8c0e3d2aaadc02c80bcd70002c8f3a2b420766bdbf0583472f111571ad7a + sourceBlobDigest: sha256:eb396b615503ccba8356cf7a2addcbc35b14bc388053dff59504e74cf0bdba3d tags: - latest - 2.2.0 @@ -25,8 +25,8 @@ targets: autumn-python: source: Autumn API Stripped sourceNamespace: autumn-api-stripped - sourceRevisionDigest: sha256:73b97e1e3131f4a268daddbc9107f4e77beaf628f9308078516cb84848a94480 - sourceBlobDigest: sha256:125b8a9f6111ceb84655b9f7eae781f5a566c8946bee87632a501e382f0a06db + sourceRevisionDigest: sha256:07eb8c0e3d2aaadc02c80bcd70002c8f3a2b420766bdbf0583472f111571ad7a + sourceBlobDigest: sha256:eb396b615503ccba8356cf7a2addcbc35b14bc388053dff59504e74cf0bdba3d codeSamplesNamespace: autumn-api-python-code-samples codeSamplesRevisionDigest: sha256:9804d178e8ccbe6f452890582dfdd15bdcd88c54ad12ffaec9e7b887b5792082 workflow: diff --git a/packages/sdk/src/models/preview-attach-op.ts b/packages/sdk/src/models/preview-attach-op.ts index 69b603db9..397445fae 100644 --- a/packages/sdk/src/models/preview-attach-op.ts +++ b/packages/sdk/src/models/preview-attach-op.ts @@ -5,7 +5,8 @@ import * as z from "zod/v4-mini"; import { remap as remap$ } from "../lib/primitives.js"; import { safeParse } from "../lib/schemas.js"; -import { ClosedEnum } from "../types/enums.js"; +import * as openEnums from "../types/enums.js"; +import { ClosedEnum, OpenEnum } from "../types/enums.js"; import { Result as SafeParseResult } from "../types/fp.js"; import * as types from "../types/primitives.js"; import { smartUnion } from "../types/smart-union.js"; @@ -794,6 +795,14 @@ export type PreviewAttachOutgoing = { expiresAt: number | null; }; +export const PreviewAttachCheckoutType = { + StripeCheckout: "stripe_checkout", + AutumnCheckout: "autumn_checkout", +} as const; +export type PreviewAttachCheckoutType = OpenEnum< + typeof PreviewAttachCheckoutType +>; + /** * OK */ @@ -802,9 +811,6 @@ export type PreviewAttachResponse = { * The ID of the customer. */ customerId: string; - /** - * List of line items for the current billing period. - */ lineItems: Array; /** * The total amount in cents before discounts for the current billing period. @@ -834,6 +840,14 @@ export type PreviewAttachResponse = { * Products or subscription changes being removed or ended. */ outgoing: Array; + /** + * Whether the customer will be redirected to a checkout page if attach is called. + */ + redirectToCheckout: boolean; + /** + * The type of checkout that will be used if the customer is redirected to a checkout page. + */ + checkoutType: PreviewAttachCheckoutType | null; }; /** @internal */ @@ -1906,6 +1920,12 @@ export function previewAttachOutgoingFromJSON( ); } +/** @internal */ +export const PreviewAttachCheckoutType$inboundSchema: z.ZodMiniType< + PreviewAttachCheckoutType, + unknown +> = openEnums.inboundSchema(PreviewAttachCheckoutType); + /** @internal */ export const PreviewAttachResponse$inboundSchema: z.ZodMiniType< PreviewAttachResponse, @@ -1923,12 +1943,16 @@ export const PreviewAttachResponse$inboundSchema: z.ZodMiniType< expand: types.optional(z.array(types.string())), incoming: z.array(z.lazy(() => PreviewAttachIncoming$inboundSchema)), outgoing: z.array(z.lazy(() => PreviewAttachOutgoing$inboundSchema)), + redirect_to_checkout: types.boolean(), + checkout_type: types.nullable(PreviewAttachCheckoutType$inboundSchema), }), z.transform((v) => { return remap$(v, { "customer_id": "customerId", "line_items": "lineItems", "next_cycle": "nextCycle", + "redirect_to_checkout": "redirectToCheckout", + "checkout_type": "checkoutType", }); }), ); diff --git a/packages/sdk/src/models/preview-multi-attach-op.ts b/packages/sdk/src/models/preview-multi-attach-op.ts index 16920a58e..0867cc377 100644 --- a/packages/sdk/src/models/preview-multi-attach-op.ts +++ b/packages/sdk/src/models/preview-multi-attach-op.ts @@ -5,7 +5,8 @@ import * as z from "zod/v4-mini"; import { remap as remap$ } from "../lib/primitives.js"; import { safeParse } from "../lib/schemas.js"; -import { ClosedEnum } from "../types/enums.js"; +import * as openEnums from "../types/enums.js"; +import { ClosedEnum, OpenEnum } from "../types/enums.js"; import { Result as SafeParseResult } from "../types/fp.js"; import * as types from "../types/primitives.js"; import { smartUnion } from "../types/smart-union.js"; @@ -801,6 +802,14 @@ export type PreviewMultiAttachOutgoing = { expiresAt: number | null; }; +export const PreviewMultiAttachCheckoutType = { + StripeCheckout: "stripe_checkout", + AutumnCheckout: "autumn_checkout", +} as const; +export type PreviewMultiAttachCheckoutType = OpenEnum< + typeof PreviewMultiAttachCheckoutType +>; + /** * OK */ @@ -809,9 +818,6 @@ export type PreviewMultiAttachResponse = { * The ID of the customer. */ customerId: string; - /** - * List of line items for the current billing period. - */ lineItems: Array; /** * The total amount in cents before discounts for the current billing period. @@ -841,6 +847,14 @@ export type PreviewMultiAttachResponse = { * Products or subscription changes being removed or ended. */ outgoing: Array; + /** + * Whether the customer will be redirected to a checkout page if attach is called. + */ + redirectToCheckout: boolean; + /** + * The type of checkout that will be used if the customer is redirected to a checkout page. + */ + checkoutType: PreviewMultiAttachCheckoutType | null; }; /** @internal */ @@ -2008,6 +2022,12 @@ export function previewMultiAttachOutgoingFromJSON( ); } +/** @internal */ +export const PreviewMultiAttachCheckoutType$inboundSchema: z.ZodMiniType< + PreviewMultiAttachCheckoutType, + unknown +> = openEnums.inboundSchema(PreviewMultiAttachCheckoutType); + /** @internal */ export const PreviewMultiAttachResponse$inboundSchema: z.ZodMiniType< PreviewMultiAttachResponse, @@ -2025,12 +2045,16 @@ export const PreviewMultiAttachResponse$inboundSchema: z.ZodMiniType< expand: types.optional(z.array(types.string())), incoming: z.array(z.lazy(() => PreviewMultiAttachIncoming$inboundSchema)), outgoing: z.array(z.lazy(() => PreviewMultiAttachOutgoing$inboundSchema)), + redirect_to_checkout: types.boolean(), + checkout_type: types.nullable(PreviewMultiAttachCheckoutType$inboundSchema), }), z.transform((v) => { return remap$(v, { "customer_id": "customerId", "line_items": "lineItems", "next_cycle": "nextCycle", + "redirect_to_checkout": "redirectToCheckout", + "checkout_type": "checkoutType", }); }), ); diff --git a/shared/api/billing/common/attachPreviewResponse.ts b/shared/api/billing/common/attachPreviewResponse.ts index c9da27dca..144f66931 100644 --- a/shared/api/billing/common/attachPreviewResponse.ts +++ b/shared/api/billing/common/attachPreviewResponse.ts @@ -1,10 +1,5 @@ import { z } from "zod/v4"; -import { - BillingPreviewResponseSchema, - ExtBillingPreviewResponseSchema, -} from "./billingPreviewResponse.js"; - -export const ExtAttachPreviewResponseSchema = ExtBillingPreviewResponseSchema; +import { BillingPreviewResponseSchema } from "./billingPreviewResponse.js"; export const AttachPreviewResponseSchema = BillingPreviewResponseSchema.extend({ object: z.literal("attach_preview").meta({ internal: true }), @@ -23,6 +18,7 @@ export const AttachPreviewResponseSchema = BillingPreviewResponseSchema.extend({ // redirect_type: z.enum(["stripe_checkout", "autumn_checkout", "none"]), }); +export const ExtAttachPreviewResponseSchema = AttachPreviewResponseSchema; export type ExtAttachPreviewResponse = z.infer< typeof ExtAttachPreviewResponseSchema >; From 0dde2b37fad458e43bc80e1969d949e9680b8055 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 23 Mar 2026 19:38:17 +0000 Subject: [PATCH 39/49] fix: reward indexes --- .../internal/rewards/RewardRedemptionService.ts | 16 +++++++++------- shared/models/cusModels/cusTable.ts | 1 + .../referralModels/referralCodeTable.ts | 7 +++++++ .../referralModels/rewardRedemptionTable.ts | 5 +++++ 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/server/src/internal/rewards/RewardRedemptionService.ts b/server/src/internal/rewards/RewardRedemptionService.ts index c9b03ab07..1d9ca261c 100644 --- a/server/src/internal/rewards/RewardRedemptionService.ts +++ b/server/src/internal/rewards/RewardRedemptionService.ts @@ -8,7 +8,7 @@ import { rewardRedemptions, rewards, } from "@autumn/shared"; -import { and, eq, inArray, or } from "drizzle-orm"; +import { and, eq, or, sql } from "drizzle-orm"; import type { DrizzleCli } from "@/db/initDrizzle.js"; import RecaseError from "@/utils/errorUtils.js"; @@ -95,7 +95,7 @@ export class RewardRedemptionService { ) .innerJoin( customers, - eq(rewardRedemptions.internal_customer_id, customers.internal_id), + sql`${rewardRedemptions.internal_customer_id} COLLATE "C" = ${customers.internal_id}`, ); if (withRewardProgram) { @@ -108,7 +108,9 @@ export class RewardRedemptionService { ); } const data = await query - .where(eq(referralCodes.internal_customer_id, internalCustomerId)) + .where( + sql`${referralCodes.internal_customer_id} COLLATE "C" = ${internalCustomerId}`, + ) .limit(limit); const processed = data.map((d) => ({ @@ -187,7 +189,7 @@ export class RewardRedemptionService { ) .innerJoin( customers, - eq(rewardRedemptions.internal_customer_id, customers.internal_id), + sql`${rewardRedemptions.internal_customer_id} COLLATE "C" = ${customers.internal_id}`, ) .innerJoin( rewardPrograms, @@ -203,12 +205,12 @@ export class RewardRedemptionService { .where( or( and( - eq(referralCodes.internal_customer_id, internalCustomerId), + sql`${referralCodes.internal_customer_id} COLLATE "C" = ${internalCustomerId}`, eq(rewardRedemptions.triggered, true), eq(rewardRedemptions.applied, false), ), and( - eq(rewardRedemptions.internal_customer_id, internalCustomerId), + sql`${rewardRedemptions.internal_customer_id} COLLATE "C" = ${internalCustomerId}`, eq(rewardRedemptions.triggered, true), eq(rewardRedemptions.redeemer_applied, false), ), @@ -241,7 +243,7 @@ export class RewardRedemptionService { return await db .delete(rewardRedemptions) .where( - inArray(rewardRedemptions.internal_customer_id, internalCustomerId), + sql`${rewardRedemptions.internal_customer_id} COLLATE "C" = ANY(${internalCustomerId})`, ); } } diff --git a/shared/models/cusModels/cusTable.ts b/shared/models/cusModels/cusTable.ts index f73d5544c..14e59dbf3 100644 --- a/shared/models/cusModels/cusTable.ts +++ b/shared/models/cusModels/cusTable.ts @@ -61,6 +61,7 @@ export const customers = pgTable( index("idx_customers_org_env_fingerprint") .on(table.org_id, table.env, table.fingerprint) .where(sql`${table.fingerprint} IS NOT NULL`), + index("idx_customers_processor_id").on(sql`(${table.processor} ->> 'id')`), ], ).enableRLS(); diff --git a/shared/models/rewardModels/referralModels/referralCodeTable.ts b/shared/models/rewardModels/referralModels/referralCodeTable.ts index 07726d05b..aa2d36df3 100644 --- a/shared/models/rewardModels/referralModels/referralCodeTable.ts +++ b/shared/models/rewardModels/referralModels/referralCodeTable.ts @@ -1,11 +1,13 @@ import { foreignKey, + index, numeric, pgTable, primaryKey, text, unique, } from "drizzle-orm/pg-core"; +import { collatePgColumn } from "../../../db/utils.js"; import { customers } from "../../cusModels/cusTable"; import { organizations } from "../../orgModels/orgTable"; import { rewardPrograms } from "../rewardProgramModels/rewardProgramTable"; @@ -43,5 +45,10 @@ export const referralCodes = pgTable( name: "referral_codes_pkey", }), unique("referral_codes_id_key").on(table.id), + index("idx_referral_codes_internal_customer_id").on( + table.internal_customer_id, + ), ], ); + +collatePgColumn(referralCodes.internal_customer_id, "C"); diff --git a/shared/models/rewardModels/referralModels/rewardRedemptionTable.ts b/shared/models/rewardModels/referralModels/rewardRedemptionTable.ts index 734bca622..65a454978 100644 --- a/shared/models/rewardModels/referralModels/rewardRedemptionTable.ts +++ b/shared/models/rewardModels/referralModels/rewardRedemptionTable.ts @@ -1,10 +1,12 @@ import { boolean, foreignKey, + index, numeric, pgTable, text, } from "drizzle-orm/pg-core"; +import { collatePgColumn } from "../../../db/utils.js"; import { customers } from "../../cusModels/cusTable"; import { rewardPrograms } from "../rewardProgramModels/rewardProgramTable"; import { referralCodes } from "./referralCodeTable"; @@ -38,5 +40,8 @@ export const rewardRedemptions = pgTable( foreignColumns: [referralCodes.id], name: "reward_redemptions_referral_code_id_fkey", }).onDelete("cascade"), + index("idx_reward_redemptions_referral_code_id").on(table.referral_code_id), ], ); + +collatePgColumn(rewardRedemptions.internal_customer_id, "C"); From 3698373e47e8f9e1078d1268a4eac2a90e8b41c0 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Mon, 23 Mar 2026 21:08:51 +0000 Subject: [PATCH 40/49] chore: index experiments --- server/experiments/checkIndexes.ts | 46 +++++++++++ server/experiments/explainGetByStripeId.ts | 91 ++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 server/experiments/checkIndexes.ts create mode 100644 server/experiments/explainGetByStripeId.ts diff --git a/server/experiments/checkIndexes.ts b/server/experiments/checkIndexes.ts new file mode 100644 index 000000000..14d5d7e90 --- /dev/null +++ b/server/experiments/checkIndexes.ts @@ -0,0 +1,46 @@ +import { sql } from "drizzle-orm"; +import { initDrizzle } from "./experimentEnv"; + +// Run with `bun run experiments/checkIndexes.ts` + +const main = async () => { + const { db } = initDrizzle(); + + console.log("=== INDEX VALIDITY CHECK ===\n"); + + const results = await db.execute(sql` + SELECT c.relname, i.indisvalid, i.indisready, i.indislive, + pg_size_pretty(pg_relation_size(c.oid)) as size, + am.amname as type + FROM pg_index i + JOIN pg_class c ON c.oid = i.indexrelid + JOIN pg_am am ON am.oid = c.relam + WHERE c.relname IN ( + 'idx_referral_codes_internal_customer_id', + 'idx_referral_codes_internal_customer_id_v2', + 'idx_reward_redemptions_referral_code_id', + 'idx_reward_redemptions_referral_code_id_v2', + 'idx_customers_processor_gin', + 'idx_customers_processor_gin_v2', + 'idx_customers_processor_id', + 'idx_customers_processor_id_v2' + ) + ORDER BY c.relname + `); + + if (results.length === 0) { + console.log("No matching indexes found.\n"); + } + + for (const row of results) { + const r = row as Record; + const valid = r.indisvalid ? "VALID" : "INVALID"; + console.log( + `${valid} | ${r.relname} | type: ${r.type} | size: ${r.size}`, + ); + } + + process.exit(0); +}; + +await main(); diff --git a/server/experiments/explainGetByStripeId.ts b/server/experiments/explainGetByStripeId.ts new file mode 100644 index 000000000..773d9f259 --- /dev/null +++ b/server/experiments/explainGetByStripeId.ts @@ -0,0 +1,91 @@ +import { AppEnv, customers } from "@autumn/shared"; +import { and, eq, sql } from "drizzle-orm"; +import { initDrizzle, prodTestCustomerId, prodTestOrgId } from "./experimentEnv"; + +// Run with `bun run experiments/explainGetByStripeId.ts` + +const main = async () => { + const orgId = prodTestOrgId; + const env = AppEnv.Live; + + const { db } = initDrizzle(); + + // Grab a real stripe ID from the test customer + const testCus = await db.query.customers.findFirst({ + where: and( + eq(customers.id, prodTestCustomerId), + eq(customers.org_id, orgId), + eq(customers.env, env), + ), + }); + + const stripeId = testCus?.processor?.id; + if (!stripeId) { + console.error("Test customer has no processor.id"); + process.exit(1); + } + + console.log(`Using stripeId: ${stripeId}`); + console.log(`Org: ${orgId} | Env: ${env}\n`); + + // ── Check index validity ───────────────────────────────────────── + console.log("=== INDEX VALIDITY ===\n"); + const idxValid = await db.execute(sql` + SELECT c.relname, i.indisvalid, i.indisready, i.indislive, + pg_size_pretty(pg_relation_size(c.oid)) as size + FROM pg_index i + JOIN pg_class c ON c.oid = i.indexrelid + WHERE c.relname LIKE '%processor%' + `); + for (const row of idxValid) { + console.log(row); + } + + // ── Query with org/env (matches CusService.getByStripeId) ──────── + const withOrgEnv = sql` + SELECT * FROM customers + WHERE processor->>'id' = ${stripeId} + AND org_id = ${orgId} + AND env = ${env} + LIMIT 1 + `; + + console.log("\n=== QUERY: processor->>'id' + org/env ===\n"); + + const start1 = performance.now(); + const result1 = await db.execute(withOrgEnv); + const elapsed1 = performance.now() - start1; + console.log(`Rows: ${result1.length} | Time: ${elapsed1.toFixed(2)}ms\n`); + + const explain1 = await db.execute( + sql`EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) ${withOrgEnv}`, + ); + for (const row of explain1) { + console.log((row as Record)["QUERY PLAN"]); + } + + // ── Query without org/env (isolate expression index usage) ─────── + const withoutOrgEnv = sql` + SELECT * FROM customers + WHERE processor->>'id' = ${stripeId} + LIMIT 1 + `; + + console.log("\n=== QUERY: processor->>'id' only ===\n"); + + const start2 = performance.now(); + const result2 = await db.execute(withoutOrgEnv); + const elapsed2 = performance.now() - start2; + console.log(`Rows: ${result2.length} | Time: ${elapsed2.toFixed(2)}ms\n`); + + const explain2 = await db.execute( + sql`EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) ${withoutOrgEnv}`, + ); + for (const row of explain2) { + console.log((row as Record)["QUERY PLAN"]); + } + + process.exit(0); +}; + +await main(); From ad6249a936ba14b4cde41d5336565cf7abb3b321 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:30:07 +0000 Subject: [PATCH 41/49] fix: change acquireLock error code to be 423 Locked instead of 429 Too Many Requests --- server/src/external/redis/redisUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/external/redis/redisUtils.ts b/server/src/external/redis/redisUtils.ts index 8f4a47be3..b44d3c84a 100644 --- a/server/src/external/redis/redisUtils.ts +++ b/server/src/external/redis/redisUtils.ts @@ -53,8 +53,8 @@ export const acquireLock = async ({ throw new RecaseError({ message: parsed?.errorMessage || DEFAULT_ERROR_MESSAGE, - code: ErrCode.InvalidRequest, - statusCode: 429, + code: ErrCode.LockAlreadyExists, + statusCode: 423, }); } From 1eea322b18820f4f69ee9baac975f691e5829648 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Tue, 24 Mar 2026 11:49:11 +0000 Subject: [PATCH 42/49] M --- .../deleteFullCustomerCache.lua | 36 ++++++++----- .../setFullCustomerCache.lua | 52 +++++++++++++----- .../_luaScriptsV2/fullCustomerKeyBuilders.lua | 24 +++++++++ server/src/_luaScriptsV2/luaScriptsV2.ts | 32 ++++++++--- server/src/external/redis/initRedis.ts | 14 +++-- .../cache/pathIndex/buildPathIndex.ts | 53 +++++++++++++++++++ .../cache/pathIndex/pathIndexConfig.ts | 12 +++++ .../deleteCachedFullCustomer.ts | 24 ++------- .../fullCustomerCacheConfig.ts | 2 +- .../setCachedFullCustomer.ts | 20 +++---- 10 files changed, 198 insertions(+), 71 deletions(-) create mode 100644 server/src/_luaScriptsV2/fullCustomerKeyBuilders.lua create mode 100644 server/src/internal/customers/cache/pathIndex/buildPathIndex.ts create mode 100644 server/src/internal/customers/cache/pathIndex/pathIndexConfig.ts diff --git a/server/src/_luaScriptsV2/deleteFullCustomerCache/deleteFullCustomerCache.lua b/server/src/_luaScriptsV2/deleteFullCustomerCache/deleteFullCustomerCache.lua index 2b1273534..0b347af2c 100644 --- a/server/src/_luaScriptsV2/deleteFullCustomerCache/deleteFullCustomerCache.lua +++ b/server/src/_luaScriptsV2/deleteFullCustomerCache/deleteFullCustomerCache.lua @@ -4,17 +4,21 @@ Atomically: 1. Checks if test guard exists (skip if so - used in race condition tests) 2. Optionally sets the stale-write guard key (to prevent in-flight requests from writing stale data) - 3. Deletes the cache key + 3. Deletes the cache key and path index key + + All keys are constructed internally from orgId/env/customerId using the + prepended key builder functions. KEYS: - [1] testGuardKey - test guard key to check - [2] guardKey - stale-write guard key to set - [3] cacheKey - cache key to delete + [1] cacheKey - used for cluster slot routing only ARGV: - [1] guardTimestamp - timestamp for the guard - [2] guardTtl - TTL in seconds for the guard key - [3] skipGuard - "true" to skip setting guard key, "false" to set it (default behavior) + [1] orgId + [2] env + [3] customerId + [4] guardTimestamp - timestamp for the guard + [5] guardTtl - TTL in seconds for the guard key + [6] skipGuard - "true" to skip setting guard key, "false" to set it (default behavior) Returns: "SKIPPED" = test guard exists, deletion skipped @@ -22,12 +26,17 @@ "NOT_FOUND" = cache key didn't exist ]] -local testGuardKey = KEYS[1] -local guardKey = KEYS[2] -local cacheKey = KEYS[3] -local guardTimestamp = ARGV[1] -local guardTtl = tonumber(ARGV[2]) -local skipGuard = ARGV[3] == "true" +local org_id = ARGV[1] +local env = ARGV[2] +local customer_id = ARGV[3] +local guardTimestamp = ARGV[4] +local guardTtl = tonumber(ARGV[5]) +local skipGuard = ARGV[6] == "true" + +local testGuardKey = build_test_guard_key(org_id, env, customer_id) +local guardKey = build_guard_key(org_id, env, customer_id) +local cacheKey = build_full_customer_cache_key(org_id, env, customer_id) +local pathIdxKey = build_path_index_key(org_id, env, customer_id) -- Check test guard first (used in race condition tests) if redis.call("EXISTS", testGuardKey) == 1 then @@ -40,6 +49,7 @@ if not skipGuard then end local deleted = redis.call("DEL", cacheKey) +redis.call("DEL", pathIdxKey) if deleted > 0 then return "DELETED" diff --git a/server/src/_luaScriptsV2/deleteFullCustomerCache/setFullCustomerCache.lua b/server/src/_luaScriptsV2/deleteFullCustomerCache/setFullCustomerCache.lua index b84d8b2aa..b53666798 100644 --- a/server/src/_luaScriptsV2/deleteFullCustomerCache/setFullCustomerCache.lua +++ b/server/src/_luaScriptsV2/deleteFullCustomerCache/setFullCustomerCache.lua @@ -6,16 +6,23 @@ 2. Checks if cache already exists (skip if so, unless overwrite is true) 3. Sets the cache using JSON.SET 4. Sets TTL on the cache key + 5. Replaces the path index Hash (DEL + HSET + EXPIRE) + + All keys are constructed internally from orgId/env/customerId using the + prepended key builder functions. KEYS: - [1] guardKey - stale-write guard key to check - [2] cacheKey - cache key to set + [1] cacheKey - used for cluster slot routing only ARGV: - [1] fetchTimeMs - timestamp when data was fetched from Postgres - [2] cacheTtl - TTL in seconds for the cache key - [3] serializedData - JSON string of the FullCustomer data - [4] overwrite - "1" to overwrite existing cache, "0" to skip if exists + [1] orgId + [2] env + [3] customerId + [4] fetchTimeMs - timestamp when data was fetched from Postgres + [5] cacheTtl - TTL in seconds for the cache key + [6] serializedData - JSON string of the FullCustomer data + [7] overwrite - "true" to overwrite existing cache, "false" to skip if exists + [8] pathIndexJson - JSON object mapping field names to values for HSET (e.g. {"ent:id1":"{...}", ...}) Returns: "STALE_WRITE" = guard exists with newer timestamp, write blocked @@ -23,12 +30,18 @@ "OK" = cache set successfully ]] -local guardKey = KEYS[1] -local cacheKey = KEYS[2] -local fetchTimeMs = tonumber(ARGV[1]) -local cacheTtl = tonumber(ARGV[2]) -local serializedData = ARGV[3] -local overwrite = ARGV[4] == "true" +local org_id = ARGV[1] +local env = ARGV[2] +local customer_id = ARGV[3] +local fetchTimeMs = tonumber(ARGV[4]) +local cacheTtl = tonumber(ARGV[5]) +local serializedData = ARGV[6] +local overwrite = ARGV[7] == "true" +local pathIndexJson = ARGV[8] + +local guardKey = build_guard_key(org_id, env, customer_id) +local cacheKey = build_full_customer_cache_key(org_id, env, customer_id) +local pathIdxKey = build_path_index_key(org_id, env, customer_id) -- Check if guard exists (deletion happened recently) -- Skip check if either value is nil/null/falsey @@ -52,4 +65,19 @@ end redis.call("JSON.SET", cacheKey, "$", serializedData) redis.call("EXPIRE", cacheKey, cacheTtl) +-- Atomically replace the path index Hash +if pathIndexJson and pathIndexJson ~= "" then + local entries = cjson.decode(pathIndexJson) + redis.call("DEL", pathIdxKey) + local args = {} + for k, v in pairs(entries) do + args[#args + 1] = k + args[#args + 1] = v + end + if #args > 0 then + redis.call("HSET", pathIdxKey, unpack(args)) + redis.call("EXPIRE", pathIdxKey, cacheTtl) + end +end + return "OK" diff --git a/server/src/_luaScriptsV2/fullCustomerKeyBuilders.lua b/server/src/_luaScriptsV2/fullCustomerKeyBuilders.lua new file mode 100644 index 000000000..8acc0ab26 --- /dev/null +++ b/server/src/_luaScriptsV2/fullCustomerKeyBuilders.lua @@ -0,0 +1,24 @@ +--[[ + Shared key builder functions for FullCustomer cache keys. + + NOTE: FULL_CUSTOMER_CACHE_VERSION is injected by luaScriptsV2.ts at load time + (the __FULL_CUSTOMER_CACHE_VERSION__ placeholder is replaced with the real value). +]] + +local FULL_CUSTOMER_CACHE_VERSION = "__FULL_CUSTOMER_CACHE_VERSION__" + +local function build_full_customer_cache_key(org_id, env, customer_id) + return "{" .. org_id .. "}:" .. env .. ":fullcustomer:" .. FULL_CUSTOMER_CACHE_VERSION .. ":" .. customer_id +end + +local function build_guard_key(org_id, env, customer_id) + return "{" .. org_id .. "}:" .. env .. ":fullcustomer:guard:" .. customer_id +end + +local function build_test_guard_key(org_id, env, customer_id) + return "{" .. org_id .. "}:" .. env .. ":test_full_customer_cache_guard:" .. customer_id +end + +local function build_path_index_key(org_id, env, customer_id) + return "{" .. org_id .. "}:" .. env .. ":fullcustomer:pathidx:" .. customer_id +end diff --git a/server/src/_luaScriptsV2/luaScriptsV2.ts b/server/src/_luaScriptsV2/luaScriptsV2.ts index bb78aa716..010d8160e 100644 --- a/server/src/_luaScriptsV2/luaScriptsV2.ts +++ b/server/src/_luaScriptsV2/luaScriptsV2.ts @@ -1,6 +1,7 @@ import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; +import { FULL_CUSTOMER_CACHE_VERSION } from "@/internal/customers/cusUtils/fullCustomerCacheUtils/fullCustomerCacheConfig.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -118,28 +119,43 @@ ${LOCK_RECEIPT_UTILS} ${LOCK_STATE_UTILS} ${claimLockReceiptMainScript}`; +// ============================================================================ +// FULL CUSTOMER KEY BUILDER LUA (version interpolated from TS config) +// ============================================================================ + +const FULL_CUSTOMER_KEY_BUILDERS = readFileSync( + join(__dirname, "fullCustomerKeyBuilders.lua"), + "utf-8", +).replace("__FULL_CUSTOMER_CACHE_VERSION__", FULL_CUSTOMER_CACHE_VERSION); + // ============================================================================ // DELETE FULL CUSTOMER CACHE SCRIPTS // ============================================================================ -/** - * Lua script for deleting a single FullCustomer cache from Redis. - * Checks test guard, sets stale-write guard, and deletes cache atomically. - */ -export const DELETE_FULL_CUSTOMER_CACHE_SCRIPT = readFileSync( +const deleteFullCustomerCacheScript = readFileSync( join(DELETE_CACHE_DIR, "deleteFullCustomerCache.lua"), "utf-8", ); /** - * Lua script for setting a FullCustomer cache in Redis. - * Checks stale-write guard, checks if cache exists, and sets cache atomically. + * Lua script for deleting a single FullCustomer cache from Redis. + * Builds all keys internally from orgId/env/customerId. */ -export const SET_FULL_CUSTOMER_CACHE_SCRIPT = readFileSync( +export const DELETE_FULL_CUSTOMER_CACHE_SCRIPT = `${FULL_CUSTOMER_KEY_BUILDERS} +${deleteFullCustomerCacheScript}`; + +const setFullCustomerCacheScript = readFileSync( join(DELETE_CACHE_DIR, "setFullCustomerCache.lua"), "utf-8", ); +/** + * Lua script for setting a FullCustomer cache in Redis. + * Builds all keys internally from orgId/env/customerId. + */ +export const SET_FULL_CUSTOMER_CACHE_SCRIPT = `${FULL_CUSTOMER_KEY_BUILDERS} +${setFullCustomerCacheScript}`; + /** * Lua script for batch deleting multiple FullCustomer caches from Redis. * For each customer: checks test guard, sets stale-write guard, deletes cache. diff --git a/server/src/external/redis/initRedis.ts b/server/src/external/redis/initRedis.ts index 3bb31ce10..3962fd001 100644 --- a/server/src/external/redis/initRedis.ts +++ b/server/src/external/redis/initRedis.ts @@ -175,7 +175,7 @@ const configureRedisInstance = (redisInstance: Redis): Redis => { }); redisInstance.defineCommand("deleteFullCustomerCache", { - numberOfKeys: 3, + numberOfKeys: 1, lua: DELETE_FULL_CUSTOMER_CACHE_SCRIPT, }); @@ -185,7 +185,7 @@ const configureRedisInstance = (redisInstance: Redis): Redis => { }); redisInstance.defineCommand("setFullCustomerCache", { - numberOfKeys: 2, + numberOfKeys: 1, lua: SET_FULL_CUSTOMER_CACHE_SCRIPT, }); @@ -409,9 +409,10 @@ declare module "ioredis" { paramsJson: string, ): Promise; deleteFullCustomerCache( - testGuardKey: string, - guardKey: string, cacheKey: string, + orgId: string, + env: string, + customerId: string, guardTimestamp: string, guardTtl: string, skipGuard: string, @@ -422,12 +423,15 @@ declare module "ioredis" { customersJson: string, ): Promise; setFullCustomerCache( - guardKey: string, cacheKey: string, + orgId: string, + env: string, + customerId: string, fetchTimeMs: string, cacheTtl: string, serializedData: string, overwrite: string, + pathIndexJson: string, ): Promise<"STALE_WRITE" | "CACHE_EXISTS" | "OK">; resetCustomerEntitlements( cacheKey: string, diff --git a/server/src/internal/customers/cache/pathIndex/buildPathIndex.ts b/server/src/internal/customers/cache/pathIndex/buildPathIndex.ts new file mode 100644 index 000000000..61ecee1cb --- /dev/null +++ b/server/src/internal/customers/cache/pathIndex/buildPathIndex.ts @@ -0,0 +1,53 @@ +import type { FullCustomer } from "@autumn/shared"; + +/** + * Builds a Record mapping entitlement IDs to their JSON paths within + * the FullCustomer cache value, ready for HSET into the path index. + */ +export const buildPathIndex = ({ + fullCustomer, +}: { + fullCustomer: FullCustomer; +}): Record => { + const entries: Record = {}; + + for (let cpIdx = 0; cpIdx < fullCustomer.customer_products.length; cpIdx++) { + const customerProduct = fullCustomer.customer_products[cpIdx]; + for ( + let ceIdx = 0; + ceIdx < customerProduct.customer_entitlements.length; + ceIdx++ + ) { + const customerEntitlement = customerProduct.customer_entitlements[ceIdx]; + const path = `$.customer_products[${cpIdx}].customer_entitlements[${ceIdx}]`; + const entityFeatureId = + customerEntitlement.entitlement?.entity_feature_id ?? null; + + entries[`ent:${customerEntitlement.id}`] = JSON.stringify({ + p: path, + ef: entityFeatureId, + }); + } + } + + if (fullCustomer.extra_customer_entitlements) { + for ( + let eceIdx = 0; + eceIdx < fullCustomer.extra_customer_entitlements.length; + eceIdx++ + ) { + const extraCustomerEntitlement = + fullCustomer.extra_customer_entitlements[eceIdx]; + const path = `$.extra_customer_entitlements[${eceIdx}]`; + const entityFeatureId = + extraCustomerEntitlement.entitlement?.entity_feature_id ?? null; + + entries[`ent:${extraCustomerEntitlement.id}`] = JSON.stringify({ + p: path, + ef: entityFeatureId, + }); + } + } + + return entries; +}; diff --git a/server/src/internal/customers/cache/pathIndex/pathIndexConfig.ts b/server/src/internal/customers/cache/pathIndex/pathIndexConfig.ts new file mode 100644 index 000000000..6ef1a06bc --- /dev/null +++ b/server/src/internal/customers/cache/pathIndex/pathIndexConfig.ts @@ -0,0 +1,12 @@ +/** Build the Redis Hash key for the FullCustomer path index */ +export const buildPathIndexKey = ({ + orgId, + env, + customerId, +}: { + orgId: string; + env: string; + customerId: string; +}) => { + return `{${orgId}}:${env}:fullcustomer:pathidx:${customerId}`; +}; diff --git a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/deleteCachedFullCustomer.ts b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/deleteCachedFullCustomer.ts index 3b31e82bf..ac3600a61 100644 --- a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/deleteCachedFullCustomer.ts +++ b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/deleteCachedFullCustomer.ts @@ -5,11 +5,9 @@ import { } from "@/external/redis/initRedis.js"; import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js"; import { - buildFullCustomerCacheGuardKey, buildFullCustomerCacheKey, FULL_CUSTOMER_CACHE_GUARD_TTL_SECONDS, } from "./fullCustomerCacheConfig.js"; -import { buildTestFullCustomerCacheGuardKey } from "./testFullCustomerCacheGuard.js"; /** * Delete FullCustomer from Redis cache across ALL regions. @@ -30,22 +28,7 @@ export const deleteCachedFullCustomer = async ({ if (redis.status !== "ready" || !customerId) return; - const testGuardKey = buildTestFullCustomerCacheGuardKey({ - orgId: org.id, - env, - customerId, - }); - const cacheKey = buildFullCustomerCacheKey({ - orgId: org.id, - env, - customerId, - }); - const guardKey = buildFullCustomerCacheGuardKey({ - orgId: org.id, - env, - customerId, - }); - + const cacheKey = buildFullCustomerCacheKey({ orgId: org.id, env, customerId }); const regions = getConfiguredRegions(); const guardTimestamp = Date.now().toString(); @@ -60,9 +43,10 @@ export const deleteCachedFullCustomer = async ({ } const result = await regionalRedis.deleteFullCustomerCache( - testGuardKey, - guardKey, cacheKey, + org.id, + env, + customerId, guardTimestamp, FULL_CUSTOMER_CACHE_GUARD_TTL_SECONDS.toString(), skipGuard.toString(), diff --git a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/fullCustomerCacheConfig.ts b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/fullCustomerCacheConfig.ts index 69ec637e7..138b0d98c 100644 --- a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/fullCustomerCacheConfig.ts +++ b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/fullCustomerCacheConfig.ts @@ -3,7 +3,7 @@ * Separate from ApiCustomer cache to allow independent versioning */ -const FULL_CUSTOMER_CACHE_VERSION = "1.0.0"; +export const FULL_CUSTOMER_CACHE_VERSION = "1.0.0"; /** * Cache time-to-live in seconds (3 days) diff --git a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/setCachedFullCustomer.ts b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/setCachedFullCustomer.ts index d54cfc483..179b8af69 100644 --- a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/setCachedFullCustomer.ts +++ b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/setCachedFullCustomer.ts @@ -1,10 +1,10 @@ import type { FullCustomer } from "@autumn/shared"; import { redis } from "@/external/redis/initRedis.js"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; +import { buildPathIndex } from "@/internal/customers/cache/pathIndex/buildPathIndex.js"; import { tryRedisWrite } from "@/utils/cacheUtils/cacheUtils.js"; import { addToExtraLogs } from "@/utils/logging/addToExtraLogs.js"; import { - buildFullCustomerCacheGuardKey, buildFullCustomerCacheKey, FULL_CUSTOMER_CACHE_TTL_SECONDS, } from "./fullCustomerCacheConfig.js"; @@ -32,25 +32,21 @@ export const setCachedFullCustomer = async ({ }): Promise => { const { org, env, logger } = ctx; - const cacheKey = buildFullCustomerCacheKey({ - orgId: org.id, - env, - customerId, - }); - const guardKey = buildFullCustomerCacheGuardKey({ - orgId: org.id, - env, - customerId, - }); + const cacheKey = buildFullCustomerCacheKey({ orgId: org.id, env, customerId }); + const pathIndexEntries = buildPathIndex({ fullCustomer }); + const pathIndexJson = JSON.stringify(pathIndexEntries); const result = await tryRedisWrite(async () => { return await redis.setFullCustomerCache( - guardKey, cacheKey, + org.id, + env, + customerId, String(fetchTimeMs), String(FULL_CUSTOMER_CACHE_TTL_SECONDS), JSON.stringify(fullCustomer), String(overwrite), + pathIndexJson, ); }); From d4ca5837dd6880397ff1819d48da678f43a33a5d Mon Sep 17 00:00:00 2001 From: John Yeo Date: Tue, 24 Mar 2026 13:14:01 +0000 Subject: [PATCH 43/49] updated package.json for autumn-js --- apps/sdk-test/sdk.ts | 2 -- bun.lock | 2 +- packages/autumn-js/package.json | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/sdk-test/sdk.ts b/apps/sdk-test/sdk.ts index f90cafbac..cdf5d4315 100644 --- a/apps/sdk-test/sdk.ts +++ b/apps/sdk-test/sdk.ts @@ -10,5 +10,3 @@ const res = await autumn.customers.getOrCreate({ }); console.log("Res:", res); - -// console.log(JSON.stringify(entity.billingControls, null, 2)); diff --git a/bun.lock b/bun.lock index 7bc5a7a9c..4c5c1fec2 100644 --- a/bun.lock +++ b/bun.lock @@ -175,7 +175,7 @@ }, "packages/autumn-js": { "name": "autumn-js", - "version": "1.1.0", + "version": "1.1.2", "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", diff --git a/packages/autumn-js/package.json b/packages/autumn-js/package.json index 822403386..a55ca89cf 100644 --- a/packages/autumn-js/package.json +++ b/packages/autumn-js/package.json @@ -1,7 +1,7 @@ { "name": "autumn-js", "description": "Autumn JS Library", - "version": "1.1.0", + "version": "1.1.2", "repository": "github:useautumn/autumn", "homepage": "https://docs.useautumn.com", "main": "./dist/sdk/index.js", From 9d48a38c1218fd7a831c957a4e81599f0f2ea484 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Tue, 24 Mar 2026 13:49:39 +0000 Subject: [PATCH 44/49] fix: usage alert credit systems --- .../trackWebhooks/fireTrackWebhooks.ts | 47 +++--- .../deduction/executePostgresDeduction.ts | 9 +- .../utils/deduction/executeRedisDeduction.ts | 7 + .../utils/deduction/mutationLogsToFeatures.ts | 46 ++++++ .../usage-alerts-credit-system.test.ts | 148 ++++++++++++++++++ 5 files changed, 237 insertions(+), 20 deletions(-) create mode 100644 server/src/internal/balances/utils/deduction/mutationLogsToFeatures.ts create mode 100644 server/tests/integration/balances/track/usage-alerts/usage-alerts-credit-system.test.ts diff --git a/server/src/internal/balances/trackWebhooks/fireTrackWebhooks.ts b/server/src/internal/balances/trackWebhooks/fireTrackWebhooks.ts index cd91245a9..47c3a0f52 100644 --- a/server/src/internal/balances/trackWebhooks/fireTrackWebhooks.ts +++ b/server/src/internal/balances/trackWebhooks/fireTrackWebhooks.ts @@ -1,8 +1,8 @@ import type { Feature, FullCustomer } from "@autumn/shared"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; +import { checkLimitReached } from "./checkLimitReached.js"; import { checkUsageAlerts } from "./checkUsageAlerts.js"; import { handleThresholdReached } from "./handleThresholdReached.js"; -import { checkLimitReached } from "./checkLimitReached.js"; export const fireTrackWebhooks = ({ ctx, @@ -10,12 +10,14 @@ export const fireTrackWebhooks = ({ newFullCus, feature, entityId, + featuresFromMutationLogs, }: { ctx: AutumnContext; oldFullCus: FullCustomer; newFullCus: FullCustomer; feature: Feature; entityId?: string; + featuresFromMutationLogs?: Feature[]; }) => { handleThresholdReached({ ctx, @@ -26,23 +28,30 @@ export const fireTrackWebhooks = ({ ctx.logger.error(`[fireTrackWebhooks] handleThresholdReached: ${error}`); }); - checkUsageAlerts({ - ctx, - oldFullCus, - newFullCus, - feature, - entityId, - }).catch((error) => { - ctx.logger.error(`[fireTrackWebhooks] checkUsageAlerts: ${error}`); - }); + const featuresForUsageAlertsAndLimit = + featuresFromMutationLogs && featuresFromMutationLogs.length > 0 + ? featuresFromMutationLogs + : [feature]; - checkLimitReached({ - ctx, - oldFullCus, - newFullCus, - feature, - entityId, - }).catch((error) => { - ctx.logger.error(`[fireTrackWebhooks] checkLimitReached: ${error}`); - }); + for (const affectedFeature of featuresForUsageAlertsAndLimit) { + checkUsageAlerts({ + ctx, + oldFullCus, + newFullCus, + feature: affectedFeature, + entityId, + }).catch((error) => { + ctx.logger.error(`[fireTrackWebhooks] checkUsageAlerts: ${error}`); + }); + + checkLimitReached({ + ctx, + oldFullCus, + newFullCus, + feature: affectedFeature, + entityId, + }).catch((error) => { + ctx.logger.error(`[fireTrackWebhooks] checkLimitReached: ${error}`); + }); + } }; diff --git a/server/src/internal/balances/utils/deduction/executePostgresDeduction.ts b/server/src/internal/balances/utils/deduction/executePostgresDeduction.ts index d688995d4..a93e5ecff 100644 --- a/server/src/internal/balances/utils/deduction/executePostgresDeduction.ts +++ b/server/src/internal/balances/utils/deduction/executePostgresDeduction.ts @@ -10,13 +10,13 @@ import { rollbackDeduction } from "@/internal/balances/utils/paidAllocatedFeatur import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js"; import { CusService } from "../../../customers/CusService.js"; import type { EventInfo } from "../../events/initEvent.js"; +import { fireTrackWebhooks } from "../../trackWebhooks/fireTrackWebhooks.js"; import { applyDeductionUpdateToFullCustomer } from "../../utils/deduction/applyDeductionUpdateToFullCustomer.js"; import { saveLockReceipt } from "../../utils/lock/saveLockReceipt.js"; import type { DeductionUpdate } from "../../utils/types/deductionUpdate.js"; import type { FeatureDeduction } from "../../utils/types/featureDeduction.js"; import type { MutationLogItem } from "../../utils/types/mutationLogItem.js"; import { createAllocatedInvoice } from "../allocatedInvoice/createAllocatedInvoice.js"; -import { fireTrackWebhooks } from "../../trackWebhooks/fireTrackWebhooks.js"; import type { DeductionOptions } from "../types/deductionTypes.js"; import { applyRolloverUpdatesToFullCustomer } from "./applyRolloverUpdatesToFullCustomer.js"; import { @@ -24,6 +24,7 @@ import { syncCustomerEntitlementUpdatesToCache, } from "./executeDeductionCache.js"; import { logDeductionUpdates } from "./logDeductionUpdates.js"; +import { mutationLogsToFeatures } from "./mutationLogsToFeatures.js"; import { prepareDeductionOptions } from "./prepareDeductionOptions.js"; import { prepareFeatureDeduction } from "./prepareFeatureDeduction.js"; @@ -228,12 +229,18 @@ export const executePostgresDeduction = async ({ throw error; } + const featuresFromMutationLogs = mutationLogsToFeatures({ + fullCustomer, + mutationLogs: mutation_logs ?? [], + }); + fireTrackWebhooks({ ctx, oldFullCus, newFullCus: fullCustomer, feature: deduction.feature, entityId, + featuresFromMutationLogs, }); if (resolvedOptions.triggerAutoTopUp) { diff --git a/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts b/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts index 9279ee35f..86dca2661 100644 --- a/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts +++ b/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts @@ -24,6 +24,7 @@ import type { RolloverUpdate } from "../types/rolloverUpdate.js"; import { applyDeductionUpdateToFullCustomer } from "./applyDeductionUpdateToFullCustomer.js"; import { applyRolloverUpdatesToFullCustomer } from "./applyRolloverUpdatesToFullCustomer.js"; import { logDeductionUpdates } from "./logDeductionUpdates.js"; +import { mutationLogsToFeatures } from "./mutationLogsToFeatures.js"; import { prepareDeductionOptions } from "./prepareDeductionOptions.js"; import { prepareFeatureDeduction } from "./prepareFeatureDeduction.js"; @@ -235,12 +236,18 @@ export const executeRedisDeduction = async ({ throw error; } + const featuresFromMutationLogs = mutationLogsToFeatures({ + fullCustomer, + mutationLogs: mutation_logs, + }); + fireTrackWebhooks({ ctx, oldFullCus, newFullCus: fullCustomer, feature: deduction.feature, entityId, + featuresFromMutationLogs, }); if (options.triggerAutoTopUp) { diff --git a/server/src/internal/balances/utils/deduction/mutationLogsToFeatures.ts b/server/src/internal/balances/utils/deduction/mutationLogsToFeatures.ts new file mode 100644 index 000000000..30cc00a2d --- /dev/null +++ b/server/src/internal/balances/utils/deduction/mutationLogsToFeatures.ts @@ -0,0 +1,46 @@ +import type { Feature, FullCustomer } from "@autumn/shared"; +import { fullCustomerToCustomerEntitlements } from "@autumn/shared"; +import type { MutationLogItem } from "../types/mutationLogItem.js"; + +/** Maps customer entitlement and rollover mutation targets to their features (deduped by feature id). */ +export const mutationLogsToFeatures = ({ + fullCustomer, + mutationLogs, +}: { + fullCustomer: FullCustomer; + mutationLogs: MutationLogItem[]; +}): Feature[] => { + const customerEntitlements = fullCustomerToCustomerEntitlements({ + fullCustomer, + }); + + const customerEntitlementIdToFeature = new Map(); + const rolloverIdToFeature = new Map(); + + for (const customerEntitlement of customerEntitlements) { + const feature = customerEntitlement.entitlement.feature; + customerEntitlementIdToFeature.set(customerEntitlement.id, feature); + for (const rollover of customerEntitlement.rollovers ?? []) { + rolloverIdToFeature.set(rollover.id, feature); + } + } + + const featuresById = new Map(); + + for (const log of mutationLogs) { + if ( + log.target_type === "customer_entitlement" && + log.customer_entitlement_id + ) { + const resolved = customerEntitlementIdToFeature.get( + log.customer_entitlement_id, + ); + if (resolved) featuresById.set(resolved.id, resolved); + } else if (log.target_type === "rollover" && log.rollover_id) { + const resolved = rolloverIdToFeature.get(log.rollover_id); + if (resolved) featuresById.set(resolved.id, resolved); + } + } + + return [...featuresById.values()]; +}; diff --git a/server/tests/integration/balances/track/usage-alerts/usage-alerts-credit-system.test.ts b/server/tests/integration/balances/track/usage-alerts/usage-alerts-credit-system.test.ts new file mode 100644 index 000000000..04436c9cd --- /dev/null +++ b/server/tests/integration/balances/track/usage-alerts/usage-alerts-credit-system.test.ts @@ -0,0 +1,148 @@ +/** + * Usage alert webhooks when tracking metered features that deduct a shared credit pool. + */ + +import { afterAll, beforeAll, expect, test } from "bun:test"; +import type { LimitedItem } from "@autumn/shared"; +import { + getTestSvixAppId, + setupWebhookTest, + type WebhookTestSetup, + waitForWebhook, +} from "@tests/integration/utils/svixWebhookTestUtils.js"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import ctx from "@tests/utils/testInitUtils/createTestContext.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js"; +import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js"; +import { setCustomerUsageAlerts } from "../../utils/usage-alert-utils/customerUsageAlertUtils.js"; + +type BalancesUsageAlertTriggeredPayload = { + type: string; + data: { + customer_id: string; + feature_id: string; + usage_alert: { + threshold: number; + threshold_type: string; + }; + }; +}; + +const creditsItem = constructFeatureItem({ + featureId: TestFeature.Credits, + includedUsage: 100, +}) as LimitedItem; + +const creditSystemProduct = constructProduct({ + type: "pro", + isDefault: false, + items: [creditsItem], + id: "ua-credit-system-pro", +}); + +let webhook: WebhookTestSetup; +let playToken: string; + +beforeAll(async () => { + const applicationId = getTestSvixAppId({ svixConfig: ctx.org.svix_config }); + webhook = await setupWebhookTest({ + appId: applicationId, + filterTypes: ["balances.usage_alert_triggered"], + }); + playToken = webhook.playToken; +}); + +afterAll(async () => { + await webhook?.cleanup(); +}); + +test(`${chalk.yellowBright("usage-alert credit system: track action1 triggers alert on credits feature")}`, async () => { + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "usage-alert-credit-action1", + setup: [ + s.customer({ testClock: false }), + s.products({ list: [creditSystemProduct] }), + ], + actions: [s.attach({ productId: creditSystemProduct.id })], + }); + + await setCustomerUsageAlerts({ + autumn: autumnV2_1, + customerId, + usageAlerts: [ + { + feature_id: TestFeature.Credits, + threshold: 30, + threshold_type: "usage", + enabled: true, + }, + ], + }); + + // action1 credit_cost 0.2 → 160 units => 32 credits usage (crosses 30) + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action1, + value: 160, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.usage_alert_triggered" && + payload.data?.customer_id === customerId && + payload.data?.feature_id === TestFeature.Credits && + payload.data?.usage_alert?.threshold === 30, + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + expect(result?.payload.data.feature_id).toBe(TestFeature.Credits); + expect(result?.payload.data.usage_alert.threshold_type).toBe("usage"); +}); + +test(`${chalk.yellowBright("usage-alert credit system: track action2 triggers alert on credits feature")}`, async () => { + const { customerId, autumnV2_1 } = await initScenario({ + customerId: "usage-alert-credit-action2", + setup: [ + s.customer({ testClock: false }), + s.products({ list: [creditSystemProduct] }), + ], + actions: [s.attach({ productId: creditSystemProduct.id })], + }); + + await setCustomerUsageAlerts({ + autumn: autumnV2_1, + customerId, + usageAlerts: [ + { + feature_id: TestFeature.Credits, + threshold: 30, + threshold_type: "usage", + enabled: true, + }, + ], + }); + + // action2 credit_cost 0.6 → 50 units => 30 credits usage (crosses threshold) + await autumnV2_1.track({ + customer_id: customerId, + feature_id: TestFeature.Action2, + value: 50, + }); + + const result = await waitForWebhook({ + token: playToken, + predicate: (payload) => + payload.type === "balances.usage_alert_triggered" && + payload.data?.customer_id === customerId && + payload.data?.feature_id === TestFeature.Credits && + payload.data?.usage_alert?.threshold === 30, + timeoutMs: 15000, + }); + + expect(result).not.toBeNull(); + expect(result?.payload.data.feature_id).toBe(TestFeature.Credits); +}); From f12f21a9ad37b245f9159dbdfda65d1f8a9632d6 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Tue, 24 Mar 2026 13:24:52 +0000 Subject: [PATCH 45/49] fix: use entitlement index for json path --- server/perf/redis-bench/artillery.yml | 51 ++ server/perf/redis-bench/processor.mjs | 25 + server/perf/redis-bench/setup.ts | 157 ++++++ .../contextUtils.lua | 108 ++-- .../deductFromCustomerEntitlements.lua | 58 +- .../luaUtils.lua | 54 +- .../spendLimitUtils.lua | 35 +- .../fullCustomer/fullCustomerUtils.lua | 100 ++++ .../_luaScriptsV2/fullCustomerKeyBuilders.lua | 4 +- server/src/_luaScriptsV2/luaScriptsV2.ts | 32 +- .../utils/deduction/executeRedisDeduction.ts | 3 + .../cache/pathIndex/buildPathIndex.ts | 18 +- server/tests/_temp/temp.test.ts | 516 ++++++++++++++++-- 13 files changed, 987 insertions(+), 174 deletions(-) create mode 100644 server/perf/redis-bench/artillery.yml create mode 100644 server/perf/redis-bench/processor.mjs create mode 100644 server/perf/redis-bench/setup.ts create mode 100644 server/src/_luaScriptsV2/fullCustomer/fullCustomerUtils.lua diff --git a/server/perf/redis-bench/artillery.yml b/server/perf/redis-bench/artillery.yml new file mode 100644 index 000000000..f9d689816 --- /dev/null +++ b/server/perf/redis-bench/artillery.yml @@ -0,0 +1,51 @@ +# Redis benchmark load test — stresses check/track for a single large customer +# to verify path index optimization holds under concurrent load. +# +# Prerequisites: +# 1. Run setup: cd server && bun perf/redis-bench/setup.ts +# 2. Start server: bun dev +# 3. Run: npx artillery run perf/redis-bench/artillery.yml + +config: + target: "http://localhost:8080" + processor: "./processor.mjs" + + phases: + - duration: 30 + arrivalRate: 5 + rampTo: 50 + name: "Ramp" + + - duration: 120 + arrivalRate: 50 + name: "Sustained" + + - duration: 30 + arrivalRate: 50 + rampTo: 100 + name: "Spike" + + defaults: + headers: + Authorization: "Bearer {{ $processEnvironment.UNIT_TEST_AUTUMN_SECRET_KEY }}" + Content-Type: "application/json" + +scenarios: + - name: "Check + Track (large customer, random entity)" + weight: 1 + beforeScenario: "setContext" + flow: + - post: + url: "/v1/check" + json: + customer_id: "{{ customerId }}" + feature_id: "{{ featureId }}" + entity_id: "{{ entityId }}" + + - post: + url: "/v1/track" + json: + customer_id: "{{ customerId }}" + feature_id: "{{ featureId }}" + entity_id: "{{ entityId }}" + value: 1 diff --git a/server/perf/redis-bench/processor.mjs b/server/perf/redis-bench/processor.mjs new file mode 100644 index 000000000..6db61adc3 --- /dev/null +++ b/server/perf/redis-bench/processor.mjs @@ -0,0 +1,25 @@ +/** + * Artillery processor for the Redis benchmark load test. + * + * Always targets the same large customer (the point is to stress Redis + * with the worst-case key size). Randomises the entity to spread load + * across different path index entries. + */ + +const CUSTOMER_ID = "redis-bench-large-cus"; +const ENTITY_COUNT = 100; +const CUS_ENTS_PER_PRODUCT = 10; + +/** + * Called before each virtual user scenario. + * Sets customerId, featureId, and a random entityId. + */ +export function setContext(ctx, _events, done) { + const entityIdx = Math.floor(Math.random() * ENTITY_COUNT); + const featureIdx = Math.floor(Math.random() * CUS_ENTS_PER_PRODUCT); + + ctx.vars.customerId = CUSTOMER_ID; + ctx.vars.featureId = `feature_${featureIdx}`; + ctx.vars.entityId = `entity_${entityIdx}`; + done(); +} diff --git a/server/perf/redis-bench/setup.ts b/server/perf/redis-bench/setup.ts new file mode 100644 index 000000000..0a16a9f6e --- /dev/null +++ b/server/perf/redis-bench/setup.ts @@ -0,0 +1,157 @@ +/** + * Redis benchmark setup — seeds a large FullCustomer (100 entities x 10 cusEnts) + * directly into Redis and creates a minimal customer via the Autumn API so that + * check/track endpoints can resolve it. + * + * Run: cd server && bun perf/redis-bench/setup.ts + */ + +import { loadLocalEnv } from "../../src/utils/envUtils.js"; +loadLocalEnv(); + +import { AppEnv, ApiVersion, type FullCustomer } from "@autumn/shared"; +import { customerEntitlements } from "@tests/utils/fixtures/db/customerEntitlements.js"; +import { customerProducts } from "@tests/utils/fixtures/db/customerProducts.js"; +import { customers } from "@tests/utils/fixtures/db/customers.js"; +import { entities as entityFixtures } from "@tests/utils/fixtures/db/entities.js"; +import { buildPathIndex } from "@/internal/customers/cache/pathIndex/buildPathIndex.js"; +import { + buildFullCustomerCacheKey, + FULL_CUSTOMER_CACHE_TTL_SECONDS, +} from "@/internal/customers/cusUtils/fullCustomerCacheUtils/fullCustomerCacheConfig.js"; +import { redis } from "@/external/redis/initRedis.js"; +import { AutumnInt } from "@/external/autumn/autumnCli.js"; + +const ENTITY_COUNT = 100; +const CUS_ENTS_PER_PRODUCT = 10; +const ORG_ID = process.env.TESTS_ORG_ID || "org_perf_test"; +const ENV = AppEnv.Sandbox; +const CUSTOMER_ID = "redis-bench-large-cus"; +const FEATURE_ID = "feature_0"; + +async function main() { + console.log("=== Redis Benchmark Setup ===\n"); + + // 1. Build large FullCustomer + console.log( + `Building FullCustomer: ${ENTITY_COUNT} entities x ${CUS_ENTS_PER_PRODUCT} cusEnts = ${ENTITY_COUNT * CUS_ENTS_PER_PRODUCT} total cusEnts`, + ); + + const entitiesList = Array.from({ length: ENTITY_COUNT }, (_, i) => + entityFixtures.create({ + id: `entity_${i}`, + featureId: `entity_feature_${i}`, + }), + ); + + const cusProducts = entitiesList.map((entity, entityIdx) => { + const cusEnts = Array.from({ length: CUS_ENTS_PER_PRODUCT }, (_, ceIdx) => + customerEntitlements.create({ + id: `cus_ent_${entityIdx}_${ceIdx}`, + featureId: `feature_${ceIdx}`, + featureName: `Feature ${ceIdx}`, + allowance: 1000, + balance: 500, + customerProductId: `cus_prod_entity_${entityIdx}`, + entityFeatureId: entity.feature_id, + entities: { + [entity.id!]: { id: entity.id!, balance: 500, adjustment: 0 }, + }, + }), + ); + + return customerProducts.create({ + id: `cus_prod_entity_${entityIdx}`, + productId: `prod_entity_${entityIdx}`, + customerEntitlements: cusEnts, + internalEntityId: entity.internal_id, + entityId: entity.id!, + }); + }); + + const fullCustomer: FullCustomer = { + ...customers.create({ customerProducts: cusProducts }), + id: CUSTOMER_ID, + org_id: ORG_ID, + env: ENV, + entities: entitiesList, + extra_customer_entitlements: [], + }; + + const serialized = JSON.stringify(fullCustomer); + console.log( + ` Serialized size: ${(serialized.length / 1024 / 1024).toFixed(2)} MB`, + ); + + // 2. Write to Redis + const cacheKey = buildFullCustomerCacheKey({ + orgId: ORG_ID, + env: ENV, + customerId: CUSTOMER_ID, + }); + + const pathIndexEntries = buildPathIndex({ fullCustomer }); + const pathIndexJson = JSON.stringify(pathIndexEntries); + console.log( + ` Path index: ${Object.keys(pathIndexEntries).length} entries, ${(pathIndexJson.length / 1024).toFixed(2)} KB`, + ); + + const result = await redis.setFullCustomerCache( + cacheKey, + ORG_ID, + ENV, + CUSTOMER_ID, + String(Date.now()), + String(FULL_CUSTOMER_CACHE_TTL_SECONDS), + serialized, + "true", + pathIndexJson, + ); + + console.log(` Redis setFullCustomerCache result: ${result}`); + + // 3. Create customer via Autumn API (so check/track endpoints work) + const secretKey = process.env.UNIT_TEST_AUTUMN_SECRET_KEY; + if (secretKey) { + console.log("\nCreating customer via Autumn API..."); + const autumn = new AutumnInt({ + version: ApiVersion.V1_2, + secretKey, + }); + + try { + await autumn.customers.delete(CUSTOMER_ID); + } catch { + // May not exist yet + } + + try { + await autumn.customers.create({ + id: CUSTOMER_ID, + name: "Redis Bench Large Customer", + email: "redis-bench@test.local", + }); + console.log(` Created customer: ${CUSTOMER_ID}`); + } catch (error) { + console.warn(` Could not create customer via API: ${error}`); + } + } else { + console.log( + "\n Skipping Autumn API customer creation (UNIT_TEST_AUTUMN_SECRET_KEY not set)", + ); + } + + console.log(`\n=== Setup complete ===`); + console.log(` Customer ID: ${CUSTOMER_ID}`); + console.log(` Feature ID: ${FEATURE_ID}`); + console.log(` Entity IDs: entity_0 through entity_${ENTITY_COUNT - 1}`); +} + +main() + .catch((error) => { + console.error("Setup failed:", error); + process.exit(1); + }) + .finally(() => { + process.exit(0); + }); diff --git a/server/src/_luaScriptsV2/deductFromCustomerEntitlements/contextUtils.lua b/server/src/_luaScriptsV2/deductFromCustomerEntitlements/contextUtils.lua index c96f7ec1c..1eb4e8357 100644 --- a/server/src/_luaScriptsV2/deductFromCustomerEntitlements/contextUtils.lua +++ b/server/src/_luaScriptsV2/deductFromCustomerEntitlements/contextUtils.lua @@ -13,7 +13,9 @@ params: cache_key: string customer_entitlement_ids: array of customer_entitlement IDs - full_customer: decoded FullCustomer object + full_customer: decoded FullCustomer object (nil when path index is available) + pathidx_key: string (path index Redis Hash key) + has_pathidx: boolean (true when path index exists) Returns: context table with: customer_entitlements: { [cus_ent_id]: { base_path, balance, adjustment, entities } } @@ -25,11 +27,16 @@ ]] local function init_context(params) local logs = {} + local has_pathidx = params.has_pathidx + local pathidx_key = params.pathidx_key local context = { customer_entitlements = {}, rollovers = {}, + cache_key = params.cache_key, full_customer = params.full_customer, + pathidx_key = pathidx_key, + has_pathidx = has_pathidx, mutation_logs = {}, pending_writes = {}, logs = logs, @@ -41,65 +48,90 @@ local function init_context(params) } for _, ent_id in ipairs(params.customer_entitlement_ids or {}) do - local cus_ent, cus_product, ce_idx, cp_idx = find_entitlement(params.full_customer, ent_id) - - if cus_ent then - local base_path - local is_loose = (cp_idx == nil) -- Loose entitlement if no customer_product index - - if is_loose then - -- Loose entitlement: path is $.extra_customer_entitlements[idx] - local ece_idx_0 = ce_idx - 1 - base_path = '$.extra_customer_entitlements[' .. ece_idx_0 .. ']' - else - -- Product entitlement: path is $.customer_products[cp_idx].customer_entitlements[ce_idx] - local cp_idx_0 = cp_idx - 1 - local ce_idx_0 = ce_idx - 1 - base_path = '$.customer_products[' .. cp_idx_0 .. '].customer_entitlements[' .. ce_idx_0 .. ']' + local base_path + local has_entity_scope + local is_loose + local adjustment + local unlimited + local cus_ent_rollovers + local cus_ent_balance + local cus_ent_entities + + if has_pathidx then + local result = get_customer_entitlement_via_index({ + pathidx_key = pathidx_key, + cache_key = params.cache_key, + cus_ent_id = ent_id, + }) + if result then + base_path = result.base_path + has_entity_scope = result.has_entity_scope + is_loose = result.is_loose + local sub = result.sub + adjustment = safe_number(sub.adjustment or 0) + unlimited = sub.unlimited + cus_ent_rollovers = sub.rollovers + cus_ent_balance = safe_number(sub.balance or 0) + cus_ent_entities = safe_table(sub.entities) end + else + -- Fallback path: decode full customer + nested loop search + local cus_ent, cus_product, ce_idx, cp_idx = find_entitlement(params.full_customer, ent_id) + + if cus_ent then + is_loose = (cp_idx == nil) + + if is_loose then + local ece_idx_0 = ce_idx - 1 + base_path = '$.extra_customer_entitlements[' .. ece_idx_0 .. ']' + else + local cp_idx_0 = cp_idx - 1 + local ce_idx_0 = ce_idx - 1 + base_path = '$.customer_products[' .. cp_idx_0 .. '].customer_entitlements[' .. ce_idx_0 .. ']' + end - local entitlement = cus_ent.entitlement - local has_entity_scope = not is_nil(entitlement) - and not is_nil(entitlement.entity_feature_id) + local entitlement = cus_ent.entitlement + has_entity_scope = not is_nil(entitlement) and not is_nil(entitlement.entity_feature_id) + adjustment = cus_ent.adjustment or 0 + unlimited = cus_ent.unlimited + cus_ent_rollovers = cus_ent.rollovers + cus_ent_balance = safe_number(cus_ent.balance or 0) + cus_ent_entities = safe_table(cus_ent.entities) + end + end + if base_path then local ent_data = { base_path = base_path, has_entity_scope = has_entity_scope, - adjustment = cus_ent.adjustment or 0, - unlimited = cus_ent.unlimited, + adjustment = adjustment, + unlimited = unlimited, is_loose = is_loose, } if has_entity_scope then - ent_data.balance = 0 -- Not used for entity-scoped - ent_data.entities = read_current_entities(params.cache_key, base_path) + ent_data.balance = 0 + ent_data.entities = cus_ent_entities or {} else - ent_data.balance = read_current_balance(params.cache_key, base_path) + ent_data.balance = cus_ent_balance or 0 ent_data.entities = nil end context.customer_entitlements[ent_id] = ent_data - -- Build rollover index for this customer_entitlement - local cus_ent_rollovers = cus_ent.rollovers if cus_ent_rollovers and type(cus_ent_rollovers) == 'table' then for r_idx, rollover in ipairs(cus_ent_rollovers) do if rollover and rollover.id then local r_idx_0 = r_idx - 1 local rollover_path = base_path .. '.rollovers[' .. r_idx_0 .. ']' - -- Read fresh rollover data from Redis - local rollover_data = read_rollover_data(params.cache_key, rollover_path) - - if rollover_data then - context.rollovers[rollover.id] = { - base_path = rollover_path, - cus_ent_id = ent_id, - balance = rollover_data.balance, - usage = rollover_data.usage, - entities = rollover_data.entities, - } - end + context.rollovers[rollover.id] = { + base_path = rollover_path, + cus_ent_id = ent_id, + balance = safe_number(rollover.balance or 0), + usage = safe_number(rollover.usage or 0), + entities = safe_table(rollover.entities), + } end end end diff --git a/server/src/_luaScriptsV2/deductFromCustomerEntitlements/deductFromCustomerEntitlements.lua b/server/src/_luaScriptsV2/deductFromCustomerEntitlements/deductFromCustomerEntitlements.lua index 724672a35..18ad670f4 100644 --- a/server/src/_luaScriptsV2/deductFromCustomerEntitlements/deductFromCustomerEntitlements.lua +++ b/server/src/_luaScriptsV2/deductFromCustomerEntitlements/deductFromCustomerEntitlements.lua @@ -10,17 +10,22 @@ 3. Pass 2: Allow negative if usage_allowed Helper functions are prepended via string interpolation from: - - luaUtils.lua (safe_table, safe_number, find_entitlement, build_entity_path, sorted_keys, is_nil) + - fullCustomerKeyBuilders.lua (build_path_index_key, etc.) + - luaUtils.lua (safe_table, safe_number, sorted_keys, is_nil) + - fullCustomerUtils.lua (find_entitlement, find_entitlement_from_index, build_entity_path, etc.) - readBalances.lua (read_current_balance, read_current_entity_balance, read_current_entities, read_rollover_data) - - contextUtils.lua (init_context, update_in_memory_customer_entitlement, queue_balance_update, apply_pending_writes) + - contextUtils.lua (init_context, queue_balance_update, apply_pending_writes) - deductFromRollovers.lua (deduct_from_rollovers) - deductFromMainBalance.lua (calculate_change, deduct_from_main_balance) - getTotalBalance.lua (get_total_balance) - KEYS[1] = FullCustomer cache key + KEYS[1] = FullCustomer cache key (used for cluster slot routing) ARGV[1] = JSON params: { + org_id: string, + env: string, + customer_id: string, sorted_entitlements: [{ customer_entitlement_id, credit_cost, feature_id, entity_feature_id, usage_allowed, min_balance, max_balance }], spend_limit_by_feature_id: { [feature_id]: { feature_id, enabled, overage_limit } } | null, usage_based_cus_ent_ids_by_feature_id: { [feature_id]: string[] } | null, @@ -51,6 +56,11 @@ local cache_key = KEYS[1] local params = cjson.decode(ARGV[1]) +-- Extract org/env/customer for path index key construction +local org_id = params.org_id +local env = params.env +local customer_id = params.customer_id + -- Extract parameters local sorted_entitlements = params.sorted_entitlements or {} local spend_limit_by_feature_id = params.spend_limit_by_feature_id @@ -70,33 +80,39 @@ local lock_receipt_key = params.lock_receipt_key -- Compute overage_behavior_is_allow once local overage_behavior_is_allow = alter_granted_balance or overage_behaviour == 'allow' --- Check if customer exists (just check the key exists) local empty_logs = cjson.decode('[]') +-- Check if customer exists local key_exists = redis.call('EXISTS', cache_key) if key_exists == 0 then return cjson.encode({ error = 'CUSTOMER_NOT_FOUND', updates = {}, rollover_updates = {}, mutation_logs = empty_logs, remaining = 0 }) end --- Get FullCustomer structure (for finding entitlement indices only) -local full_customer_json = redis.call('JSON.GET', cache_key, '.') -if not full_customer_json then - return cjson.encode({ error = 'CUSTOMER_NOT_FOUND', updates = {}, rollover_updates = {}, mutation_logs = empty_logs, remaining = 0 }) -end +-- Build path index key and check existence (fast path vs fallback) +local pathidx_key = build_path_index_key(org_id, env, customer_id) +local has_pathidx = redis.call('EXISTS', pathidx_key) == 1 -local full_customer = cjson.decode(full_customer_json) +-- Only decode full customer if path index is NOT available (fallback) +local full_customer = nil +if not has_pathidx then + local full_customer_json = redis.call('JSON.GET', cache_key, '.') + if not full_customer_json then + return cjson.encode({ error = 'CUSTOMER_NOT_FOUND', updates = {}, rollover_updates = {}, mutation_logs = empty_logs, remaining = 0 }) + end -if not full_customer.customer_products then - return cjson.encode({ - error = 'NO_CUSTOMER_PRODUCTS', - updates = {}, - rollover_updates = {}, - mutation_logs = empty_logs, - remaining = 0 - }) + full_customer = cjson.decode(full_customer_json) + + if not full_customer.customer_products then + return cjson.encode({ + error = 'NO_CUSTOMER_PRODUCTS', + updates = {}, + rollover_updates = {}, + mutation_logs = empty_logs, + remaining = 0 + }) + end end --- Track updates for return value -- Initialize context with in-memory state from Redis local customer_entitlement_ids = {} for _, ent_obj in ipairs(sorted_entitlements) do @@ -107,6 +123,8 @@ local context = init_context({ cache_key = cache_key, customer_entitlement_ids = customer_entitlement_ids, full_customer = full_customer, + pathidx_key = pathidx_key, + has_pathidx = has_pathidx, }) local unwind_modified_cus_ent_ids = {} @@ -229,7 +247,7 @@ then hashed_key = lock.hashed_key or cjson.null, status = 'pending', region = lock.region or cjson.null, - customer_id = full_customer.id or cjson.null, + customer_id = customer_id or cjson.null, feature_id = feature_id or cjson.null, entity_id = target_entity_id or cjson.null, expires_at = lock.expires_at or cjson.null, diff --git a/server/src/_luaScriptsV2/deductFromCustomerEntitlements/luaUtils.lua b/server/src/_luaScriptsV2/deductFromCustomerEntitlements/luaUtils.lua index 780cc2ca9..a31c43a19 100644 --- a/server/src/_luaScriptsV2/deductFromCustomerEntitlements/luaUtils.lua +++ b/server/src/_luaScriptsV2/deductFromCustomerEntitlements/luaUtils.lua @@ -1,11 +1,8 @@ -- ============================================================================ -- LUA UTILITIES --- Common helper functions for Lua scripts +-- Generic helper functions (no FullCustomer-specific logic) -- ============================================================================ --- ============================================================================ --- HELPER: Safe table getter (handles cjson.null) --- ============================================================================ local function safe_table(val) if val == nil or val == cjson.null or type(val) ~= 'table' then return {} @@ -13,9 +10,6 @@ local function safe_table(val) return val end --- ============================================================================ --- HELPER: Safe number getter --- ============================================================================ local function safe_number(val) if val == nil or val == cjson.null then return 0 @@ -23,56 +17,10 @@ local function safe_number(val) return tonumber(val) or 0 end --- ============================================================================ --- HELPER: Check if value is nil or cjson.null --- ============================================================================ local function is_nil(val) return val == nil or val == cjson.null end --- ============================================================================ --- HELPER: Find entitlement in FullCustomer by ID --- Returns: cus_ent table, cus_product table (or nil for loose), cus_ent_index, cus_product_index (or nil for loose) --- For loose entitlements: cus_product=nil and cus_product_index=nil --- ============================================================================ -local function find_entitlement(full_customer, ent_id) - -- Search in customer_products first - if full_customer.customer_products then - for cp_idx, cus_product in ipairs(full_customer.customer_products) do - if cus_product.customer_entitlements then - for ce_idx, cus_ent in ipairs(cus_product.customer_entitlements) do - if cus_ent.id == ent_id then - return cus_ent, cus_product, ce_idx, cp_idx - end - end - end - end - end - - -- Search in extra_customer_entitlements (loose entitlements) - if full_customer.extra_customer_entitlements then - for ece_idx, cus_ent in ipairs(full_customer.extra_customer_entitlements) do - if cus_ent.id == ent_id then - -- Return nil for cus_product and cus_product_index to indicate loose entitlement - return cus_ent, nil, ece_idx, nil - end - end - end - - return nil, nil, nil, nil -end - --- ============================================================================ --- HELPER: Build entity path (consistent across all operations) --- ============================================================================ -local function build_entity_path(base_path, entity_id) - -- Use bracket notation for entity access since entity IDs are object keys - return base_path .. '["entities"]["' .. entity_id .. '"]' -end - --- ============================================================================ --- HELPER: Get sorted keys from table (for consistent entity iteration) --- ============================================================================ local function sorted_keys(tbl) local keys = {} for k in pairs(tbl) do diff --git a/server/src/_luaScriptsV2/deductFromCustomerEntitlements/spendLimitUtils.lua b/server/src/_luaScriptsV2/deductFromCustomerEntitlements/spendLimitUtils.lua index 8d5a4eed0..ace4031d3 100644 --- a/server/src/_luaScriptsV2/deductFromCustomerEntitlements/spendLimitUtils.lua +++ b/server/src/_luaScriptsV2/deductFromCustomerEntitlements/spendLimitUtils.lua @@ -6,8 +6,39 @@ local function build_ent_data_from_full_customer(params) local full_customer = params.full_customer local cus_ent_id = params.cus_ent_id + local cache_key = params.cache_key + local pathidx_key = params.pathidx_key - if is_nil(full_customer) or is_nil(cus_ent_id) then + if is_nil(cus_ent_id) then + return nil + end + + -- Fast path: single JSON.GET on the sub-object via path index + if not is_nil(pathidx_key) then + local result = get_customer_entitlement_via_index({ + pathidx_key = pathidx_key, + cache_key = cache_key, + cus_ent_id = cus_ent_id, + }) + if is_nil(result) then return nil end + + if result.has_entity_scope then + return { + has_entity_scope = true, + balance = 0, + entities = safe_table(result.sub.entities), + } + else + return { + has_entity_scope = false, + balance = safe_number(result.sub.balance or 0), + entities = {}, + } + end + end + + -- Fallback path: decode from full customer + if is_nil(full_customer) then return nil end @@ -46,6 +77,8 @@ local function get_available_overage_from_spend_limit(params) ent_data = build_ent_data_from_full_customer({ full_customer = context.full_customer, cus_ent_id = cus_ent_id, + cache_key = context.cache_key, + pathidx_key = context.pathidx_key, }) end diff --git a/server/src/_luaScriptsV2/fullCustomer/fullCustomerUtils.lua b/server/src/_luaScriptsV2/fullCustomer/fullCustomerUtils.lua new file mode 100644 index 000000000..aa65bd6c3 --- /dev/null +++ b/server/src/_luaScriptsV2/fullCustomer/fullCustomerUtils.lua @@ -0,0 +1,100 @@ +-- ============================================================================ +-- FULL CUSTOMER UTILITIES +-- Functions for navigating the FullCustomer JSON structure in Redis +-- ============================================================================ + +-- ============================================================================ +-- Path builders: construct JSON paths from array indices +-- ============================================================================ +local function build_customer_entitlement_base_path(cp_idx, ce_idx) + return '$.customer_products[' .. cp_idx .. '].customer_entitlements[' .. ce_idx .. ']' +end + +local function build_extra_customer_entitlement_base_path(ece_idx) + return '$.extra_customer_entitlements[' .. ece_idx .. ']' +end + +-- ============================================================================ +-- Build entity path (consistent across all operations) +-- ============================================================================ +local function build_entity_path(base_path, entity_id) + return base_path .. '["entities"]["' .. entity_id .. '"]' +end + +-- ============================================================================ +-- Find entitlement in decoded FullCustomer by ID (fallback path) +-- Returns: cus_ent table, cus_product table (or nil for loose), cus_ent_index, cus_product_index (or nil for loose) +-- ============================================================================ +local function find_entitlement(full_customer, ent_id) + if full_customer.customer_products then + for cp_idx, cus_product in ipairs(full_customer.customer_products) do + if cus_product.customer_entitlements then + for ce_idx, cus_ent in ipairs(cus_product.customer_entitlements) do + if cus_ent.id == ent_id then + return cus_ent, cus_product, ce_idx, cp_idx + end + end + end + end + end + + if full_customer.extra_customer_entitlements then + for ece_idx, cus_ent in ipairs(full_customer.extra_customer_entitlements) do + if cus_ent.id == ent_id then + return cus_ent, nil, ece_idx, nil + end + end + end + + return nil, nil, nil, nil +end + +-- ============================================================================ +-- Find entitlement via the path index Hash (fast path) +-- Returns: { base_path, entity_feature_id } or nil +-- ============================================================================ +local function find_entitlement_from_index(pathidx_key, cus_ent_id) + local raw = redis.call('HGET', pathidx_key, 'cus_ent:' .. cus_ent_id) + if not raw then return nil end + local entry = cjson.decode(raw) + local base_path + if entry.ece then + base_path = build_extra_customer_entitlement_base_path(entry.ece) + else + base_path = build_customer_entitlement_base_path(entry.cp, entry.ce) + end + return { + base_path = base_path, + entity_feature_id = entry.ef, + } +end + +-- ============================================================================ +-- Fetch a customer entitlement sub-object via path index + single JSON.GET. +-- Combines index lookup and document read in one call to minimise tree traversals. +-- Returns: { base_path, has_entity_scope, is_loose, sub } or nil +-- ============================================================================ +local function get_customer_entitlement_via_index(params) + local pathidx_key = params.pathidx_key + local cache_key = params.cache_key + local cus_ent_id = params.cus_ent_id + + local idx_result = find_entitlement_from_index(pathidx_key, cus_ent_id) + if not idx_result then return nil end + + local base_path = idx_result.base_path + local sub_raw = redis.call('JSON.GET', cache_key, base_path) + if not sub_raw or sub_raw == cjson.null then return nil end + + local sub = cjson.decode(sub_raw) + if type(sub) == 'table' and sub[1] ~= nil and type(sub[1]) == 'table' then + sub = sub[1] + end + + return { + base_path = base_path, + has_entity_scope = not is_nil(idx_result.entity_feature_id), + is_loose = string.find(base_path, 'extra_customer_entitlements') ~= nil, + sub = sub, + } +end diff --git a/server/src/_luaScriptsV2/fullCustomerKeyBuilders.lua b/server/src/_luaScriptsV2/fullCustomerKeyBuilders.lua index 8acc0ab26..c362f1fcd 100644 --- a/server/src/_luaScriptsV2/fullCustomerKeyBuilders.lua +++ b/server/src/_luaScriptsV2/fullCustomerKeyBuilders.lua @@ -1,8 +1,6 @@ --[[ Shared key builder functions for FullCustomer cache keys. - - NOTE: FULL_CUSTOMER_CACHE_VERSION is injected by luaScriptsV2.ts at load time - (the __FULL_CUSTOMER_CACHE_VERSION__ placeholder is replaced with the real value). + FULL_CUSTOMER_CACHE_VERSION is injected by luaScriptsV2.ts at load time. ]] local FULL_CUSTOMER_CACHE_VERSION = "__FULL_CUSTOMER_CACHE_VERSION__" diff --git a/server/src/_luaScriptsV2/luaScriptsV2.ts b/server/src/_luaScriptsV2/luaScriptsV2.ts index 010d8160e..0bb431aa5 100644 --- a/server/src/_luaScriptsV2/luaScriptsV2.ts +++ b/server/src/_luaScriptsV2/luaScriptsV2.ts @@ -18,8 +18,15 @@ const UPDATE_DIR = join(__dirname, "updateCustomerEntitlements"); // HELPER MODULES // ============================================================================ +const FULL_CUSTOMER_DIR = join(__dirname, "fullCustomer"); + const LUA_UTILS = readFileSync(join(DEDUCT_DIR, "luaUtils.lua"), "utf-8"); +const FULL_CUSTOMER_UTILS = readFileSync( + join(FULL_CUSTOMER_DIR, "fullCustomerUtils.lua"), + "utf-8", +); + const READ_BALANCES = readFileSync( join(DEDUCT_DIR, "readBalances.lua"), "utf-8", @@ -75,6 +82,15 @@ const LOCK_UNWIND_UTILS = readFileSync( "utf-8", ); +// ============================================================================ +// FULL CUSTOMER KEY BUILDER LUA (version interpolated from TS config) +// ============================================================================ + +const FULL_CUSTOMER_KEY_BUILDERS = readFileSync( + join(__dirname, "fullCustomerKeyBuilders.lua"), + "utf-8", +).replaceAll("__FULL_CUSTOMER_CACHE_VERSION__", FULL_CUSTOMER_CACHE_VERSION); + // ============================================================================ // MAIN SCRIPT // ============================================================================ @@ -90,7 +106,9 @@ const mainScript = readFileSync( * Composed from helper modules via string interpolation. * Supports both positive deductions and negative refunds. */ -export const DEDUCT_FROM_CUSTOMER_ENTITLEMENTS_SCRIPT = `${LUA_UTILS} +export const DEDUCT_FROM_CUSTOMER_ENTITLEMENTS_SCRIPT = `${FULL_CUSTOMER_KEY_BUILDERS} +${LUA_UTILS} +${FULL_CUSTOMER_UTILS} ${READ_BALANCES} ${CONTEXT_UTILS} ${GET_TOTAL_BALANCE} @@ -119,15 +137,6 @@ ${LOCK_RECEIPT_UTILS} ${LOCK_STATE_UTILS} ${claimLockReceiptMainScript}`; -// ============================================================================ -// FULL CUSTOMER KEY BUILDER LUA (version interpolated from TS config) -// ============================================================================ - -const FULL_CUSTOMER_KEY_BUILDERS = readFileSync( - join(__dirname, "fullCustomerKeyBuilders.lua"), - "utf-8", -).replace("__FULL_CUSTOMER_CACHE_VERSION__", FULL_CUSTOMER_CACHE_VERSION); - // ============================================================================ // DELETE FULL CUSTOMER CACHE SCRIPTS // ============================================================================ @@ -178,6 +187,7 @@ const resetMainScript = readFileSync( * @deprecated Use UPDATE_CUSTOMER_ENTITLEMENTS_SCRIPT instead. */ export const RESET_CUSTOMER_ENTITLEMENTS_SCRIPT = `${LUA_UTILS} +${FULL_CUSTOMER_UTILS} ${resetMainScript}`; // ============================================================================ @@ -195,6 +205,7 @@ const updateMainScript = readFileSync( * "apply absolute values to customer entitlements in the Redis cache." */ export const UPDATE_CUSTOMER_ENTITLEMENTS_SCRIPT = `${LUA_UTILS} +${FULL_CUSTOMER_UTILS} ${updateMainScript}`; // ============================================================================ @@ -213,6 +224,7 @@ const adjustBalanceMainScript = readFileSync( * FullCustomer via JSON.NUMINCRBY. Safe with concurrent deductions. */ export const ADJUST_CUSTOMER_ENTITLEMENT_BALANCE_SCRIPT = `${LUA_UTILS} +${FULL_CUSTOMER_UTILS} ${adjustBalanceMainScript}`; // ============================================================================ diff --git a/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts b/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts index 9279ee35f..717d3d68e 100644 --- a/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts +++ b/server/src/internal/balances/utils/deduction/executeRedisDeduction.ts @@ -120,6 +120,9 @@ export const executeRedisDeduction = async ({ // Call Lua script to deduct from FullCustomer in Redis const luaParams = { + org_id: org.id, + env, + customer_id: customerId, sorted_entitlements: customerEntitlementDeductions, spend_limit_by_feature_id: spendLimitByFeatureId ?? null, usage_based_cus_ent_ids_by_feature_id: diff --git a/server/src/internal/customers/cache/pathIndex/buildPathIndex.ts b/server/src/internal/customers/cache/pathIndex/buildPathIndex.ts index 61ecee1cb..0b59fa3f8 100644 --- a/server/src/internal/customers/cache/pathIndex/buildPathIndex.ts +++ b/server/src/internal/customers/cache/pathIndex/buildPathIndex.ts @@ -1,8 +1,13 @@ import type { FullCustomer } from "@autumn/shared"; /** - * Builds a Record mapping entitlement IDs to their JSON paths within + * Builds a Record mapping entitlement IDs to their array indices within * the FullCustomer cache value, ready for HSET into the path index. + * + * Entry format for product entitlements: { cp, ce, ef } + * Entry format for extra (loose) entitlements: { ece, ef } + * + * The Lua script constructs the JSON path at read time from these indices. */ export const buildPathIndex = ({ fullCustomer, @@ -19,12 +24,12 @@ export const buildPathIndex = ({ ceIdx++ ) { const customerEntitlement = customerProduct.customer_entitlements[ceIdx]; - const path = `$.customer_products[${cpIdx}].customer_entitlements[${ceIdx}]`; const entityFeatureId = customerEntitlement.entitlement?.entity_feature_id ?? null; - entries[`ent:${customerEntitlement.id}`] = JSON.stringify({ - p: path, + entries[`cus_ent:${customerEntitlement.id}`] = JSON.stringify({ + cp: cpIdx, + ce: ceIdx, ef: entityFeatureId, }); } @@ -38,12 +43,11 @@ export const buildPathIndex = ({ ) { const extraCustomerEntitlement = fullCustomer.extra_customer_entitlements[eceIdx]; - const path = `$.extra_customer_entitlements[${eceIdx}]`; const entityFeatureId = extraCustomerEntitlement.entitlement?.entity_feature_id ?? null; - entries[`ent:${extraCustomerEntitlement.id}`] = JSON.stringify({ - p: path, + entries[`cus_ent:${extraCustomerEntitlement.id}`] = JSON.stringify({ + ece: eceIdx, ef: entityFeatureId, }); } diff --git a/server/tests/_temp/temp.test.ts b/server/tests/_temp/temp.test.ts index 90f01bace..b6c064584 100644 --- a/server/tests/_temp/temp.test.ts +++ b/server/tests/_temp/temp.test.ts @@ -1,56 +1,488 @@ -import { expect, test } from "bun:test"; -import { TestFeature } from "@tests/setup/v2Features"; -import { completeStripeCheckoutFormV2 as completeStripeCheckoutForm } from "@tests/utils/browserPool/completeStripeCheckoutFormV2"; -import { items } from "@tests/utils/fixtures/items"; -import { products } from "@tests/utils/fixtures/products"; -import { timeout } from "@tests/utils/genUtils"; -import { initScenario, s } from "@tests/utils/testInitUtils/initScenario"; +import { describe, expect, test } from "bun:test"; +import { AppEnv, type FullCustomer } from "@autumn/shared"; +import { customerEntitlements } from "@tests/utils/fixtures/db/customerEntitlements"; +import { customerProducts } from "@tests/utils/fixtures/db/customerProducts"; +import { customers } from "@tests/utils/fixtures/db/customers"; +import { entities as entityFixtures } from "@tests/utils/fixtures/db/entities"; import chalk from "chalk"; +import { redis } from "@/external/redis/initRedis.js"; +import { buildPathIndex } from "@/internal/customers/cache/pathIndex/buildPathIndex.js"; +import { buildPathIndexKey } from "@/internal/customers/cache/pathIndex/pathIndexConfig.js"; +import { + buildFullCustomerCacheKey, + FULL_CUSTOMER_CACHE_TTL_SECONDS, +} from "@/internal/customers/cusUtils/fullCustomerCacheUtils/fullCustomerCacheConfig.js"; -test.concurrent(`${chalk.yellowBright("temp: legacy checkout annual base + adjustable monthly prepaid")}`, async () => { - const customerId = "temp-legacy-checkout-annual-adjustable-prepaid"; - const includedCallMinutes = 100; - const checkoutQuantityInUnits = 300; +const ENTITY_COUNT = 100; +const CUS_ENTS_PER_PRODUCT = 10; +const ORG_ID = "org_perf_test"; +const ENV = AppEnv.Sandbox; +const CUSTOMER_ID = "cus_perf_large"; - const monthlyPrepaidCallMinutes = items.prepaid({ - featureId: TestFeature.Messages, - includedUsage: includedCallMinutes, - billingUnits: 100, - price: 13, - }); +const buildLargeFullCustomer = (): FullCustomer => { + const entitiesList = Array.from({ length: ENTITY_COUNT }, (_, i) => + entityFixtures.create({ + id: `entity_${i}`, + featureId: `entity_feature_${i}`, + }), + ); + + const cusProducts = entitiesList.map((entity, entityIdx) => { + const cusEnts = Array.from({ length: CUS_ENTS_PER_PRODUCT }, (_, ceIdx) => + customerEntitlements.create({ + id: `cus_ent_${entityIdx}_${ceIdx}`, + featureId: `feature_${ceIdx}`, + featureName: `Feature ${ceIdx}`, + allowance: 1000, + balance: 500, + customerProductId: `cus_prod_entity_${entityIdx}`, + entityFeatureId: entity.feature_id, + entities: { + [entity.id!]: { id: entity.id!, balance: 500, adjustment: 0 }, + }, + }), + ); - const smallBusiness = products.proAnnual({ - id: "small-business", - items: [monthlyPrepaidCallMinutes], + return customerProducts.create({ + id: `cus_prod_entity_${entityIdx}`, + productId: `prod_entity_${entityIdx}`, + customerEntitlements: cusEnts, + internalEntityId: entity.internal_id, + entityId: entity.id!, + }); }); - const { autumnV1 } = await initScenario({ - customerId, - setup: [ - s.customer({ testClock: true }), - s.products({ list: [smallBusiness] }), - ], - actions: [], + const fullCustomer: FullCustomer = { + ...customers.create({ customerProducts: cusProducts }), + id: CUSTOMER_ID, + org_id: ORG_ID, + env: ENV, + entities: entitiesList, + extra_customer_entitlements: [], + }; + + return fullCustomer; +}; + +const cacheKey = buildFullCustomerCacheKey({ + orgId: ORG_ID, + env: ENV, + customerId: CUSTOMER_ID, +}); + +const pathIdxKey = buildPathIndexKey({ + orgId: ORG_ID, + env: ENV, + customerId: CUSTOMER_ID, +}); + +const formatStats = ( + timings: number[], +): { min: number; avg: number; p95: number; max: number } => { + const sorted = [...timings].sort((a, b) => a - b); + const sum = sorted.reduce((acc, t) => acc + t, 0); + const p95Index = Math.floor(sorted.length * 0.95); + return { + min: sorted[0], + avg: sum / sorted.length, + p95: sorted[p95Index], + max: sorted[sorted.length - 1], + }; +}; + +const printStats = (label: string, stats: ReturnType) => { + console.log( + ` ${label}: min=${stats.min.toFixed(2)}ms avg=${stats.avg.toFixed(2)}ms p95=${stats.p95.toFixed(2)}ms max=${stats.max.toFixed(2)}ms`, + ); +}; + +// ═══════════════════════════════════════════════════════════════════ +// Block 1: Setup — seed large FullCustomer into Redis +// ═══════════════════════════════════════════════════════════════════ +describe(chalk.blueBright("setup: seed large FullCustomer"), () => { + test("seed large FullCustomer in Redis", async () => { + const fullCustomer = buildLargeFullCustomer(); + const serialized = JSON.stringify(fullCustomer); + console.log( + ` Serialized FullCustomer size: ${(serialized.length / 1024 / 1024).toFixed(2)} MB`, + ); + console.log( + ` Customer products: ${fullCustomer.customer_products.length}`, + ); + console.log( + ` Total cusEnts: ${fullCustomer.customer_products.reduce((sum, cp) => sum + cp.customer_entitlements.length, 0)}`, + ); + console.log(` Entities: ${fullCustomer.entities.length}`); + + const pathIndexEntries = buildPathIndex({ fullCustomer }); + const pathIndexJson = JSON.stringify(pathIndexEntries); + console.log( + ` Path index entries: ${Object.keys(pathIndexEntries).length}`, + ); + console.log( + ` Path index size: ${(pathIndexJson.length / 1024).toFixed(2)} KB`, + ); + + const result = await redis.setFullCustomerCache( + cacheKey, + ORG_ID, + ENV, + CUSTOMER_ID, + String(Date.now()), + String(FULL_CUSTOMER_CACHE_TTL_SECONDS), + serialized, + "true", + pathIndexJson, + ); + + expect(result).toBe("OK"); + + const exists = await redis.call("EXISTS", cacheKey); + expect(exists).toBe(1); + + const pathExists = await redis.call("EXISTS", pathIdxKey); + expect(pathExists).toBe(1); + + console.log(chalk.green(" FullCustomer seeded successfully")); }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// Helpers for building deduction params +// ═══════════════════════════════════════════════════════════════════ + +const buildDeductionParams = ({ + iteration, + entitlementCount = 1, +}: { + iteration: number; + entitlementCount?: number; +}) => { + const sortedEntitlements = Array.from( + { length: entitlementCount }, + (_, idx) => { + const entIdx = (iteration * entitlementCount + idx) % ENTITY_COUNT; + const ceIdx = (iteration * entitlementCount + idx) % CUS_ENTS_PER_PRODUCT; + return { + customer_entitlement_id: `cus_ent_${entIdx}_${ceIdx}`, + credit_cost: 1, + feature_id: `feature_${ceIdx}`, + entity_feature_id: `entity_feature_${entIdx}`, + usage_allowed: true, + min_balance: null, + max_balance: null, + }; + }, + ); + + const entityIdx = iteration % ENTITY_COUNT; + const ceIdx = iteration % CUS_ENTS_PER_PRODUCT; + return { + org_id: ORG_ID, + env: ENV, + customer_id: CUSTOMER_ID, + sorted_entitlements: sortedEntitlements, + spend_limit_by_feature_id: null, + usage_based_cus_ent_ids_by_feature_id: null, + amount_to_deduct: 1, + target_balance: null, + target_entity_id: `entity_${entityIdx}`, + rollovers: null, + skip_additional_balance: false, + alter_granted_balance: false, + overage_behaviour: "allow", + feature_id: `feature_${ceIdx}`, + lock: null, + unwind_value: null, + lock_receipt_key: null, + }; +}; + +type SlowlogEntry = [ + id: number, + timestamp: number, + durationMicros: number, + command: string[], + clientIp: string, + clientName: string, +]; + +/** + * Runs a batch of deductions and measures server-side execution time via SLOWLOG. + * SLOWLOG records commands that exceed the threshold (in microseconds). + * By setting the threshold to 0, every command is logged. + */ +const runSlowlogBenchmark = async ({ + iterations, + label, + entitlementCount = 1, +}: { + iterations: number; + label: string; + entitlementCount?: number; +}) => { + // Set threshold to 0 to capture ALL commands, keep enough entries + await redis.call("CONFIG", "SET", "slowlog-log-slower-than", "0"); + await redis.call("CONFIG", "SET", "slowlog-max-len", "1024"); + await redis.call("SLOWLOG", "RESET"); + + const e2eTimings: number[] = []; + + for (let i = 0; i < iterations; i++) { + const luaParams = buildDeductionParams({ iteration: i, entitlementCount }); + const start = performance.now(); + const result = await redis.deductFromCustomerEntitlements( + cacheKey, + JSON.stringify(luaParams), + ); + e2eTimings.push(performance.now() - start); + + const parsed = JSON.parse(result); + expect(parsed.error).toBeNull(); + } + + // Collect slowlog entries for EVALSHA commands (our Lua scripts) + const rawEntries = (await redis.call( + "SLOWLOG", + "GET", + "1024", + )) as SlowlogEntry[]; + + const evalEntries = rawEntries.filter( + (entry) => + entry[3] && (entry[3][0] === "evalsha" || entry[3][0] === "EVALSHA"), + ); + + // Duration is in microseconds (entry[2]) + const serverTimings = evalEntries.map((entry) => entry[2] / 1000); - const result = await autumnV1.attach({ - customer_id: customerId, - product_id: smallBusiness.id, - // options: [ - // { - // feature_id: TestFeature.Messages, - // adjustable: true, - // }, - // ], + // Restore default slowlog config + await redis.call("CONFIG", "SET", "slowlog-log-slower-than", "10000"); + + const e2eStats = formatStats(e2eTimings); + const serverStats = + serverTimings.length > 0 ? formatStats(serverTimings) : null; + + console.log(chalk.bold(`\n ${label} (${iterations} iterations):`)); + printStats("End-to-end (TS)", e2eStats); + if (serverStats) { + printStats("Server-side (SLOWLOG)", serverStats); + const avgRtt = e2eStats.avg - serverStats.avg; + console.log(` Network RTT (avg): ~${avgRtt.toFixed(2)}ms`); + } else { + console.log(" (no SLOWLOG entries captured for EVALSHA)"); + } + + return { e2eStats, serverStats }; +}; + +// ═══════════════════════════════════════════════════════════════════ +// Block 2: Benchmark — re-runnable against seeded data +// ═══════════════════════════════════════════════════════════════════ +describe(chalk.yellowBright("benchmark: Redis operations"), () => { + const ITERATIONS = 50; + + test("benchmark JSON.GET full read (TS-side getCachedFullCustomer cost)", async () => { + // Also measure server-side via SLOWLOG + await redis.call("CONFIG", "SET", "slowlog-log-slower-than", "0"); + await redis.call("CONFIG", "SET", "slowlog-max-len", "256"); + await redis.call("SLOWLOG", "RESET"); + + const timings: number[] = []; + for (let i = 0; i < ITERATIONS; i++) { + const start = performance.now(); + const raw = (await redis.call("JSON.GET", cacheKey)) as string | null; + expect(raw).toBeTruthy(); + JSON.parse(raw!); + const elapsed = performance.now() - start; + timings.push(elapsed); + } + + const rawEntries = (await redis.call( + "SLOWLOG", + "GET", + "256", + )) as SlowlogEntry[]; + const jsonGetEntries = rawEntries.filter( + (entry) => + entry[3] && (entry[3][0] === "JSON.GET" || entry[3][0] === "json.get"), + ); + const serverTimings = jsonGetEntries.map((entry) => entry[2] / 1000); + + await redis.call("CONFIG", "SET", "slowlog-log-slower-than", "10000"); + + const e2eStats = formatStats(timings); + const serverStats = + serverTimings.length > 0 ? formatStats(serverTimings) : null; + + console.log( + chalk.cyan(`\n JSON.GET '.' + JSON.parse (${ITERATIONS} iterations):`), + ); + printStats("End-to-end (TS)", e2eStats); + if (serverStats) { + printStats("Server-side (SLOWLOG)", serverStats); + console.log( + ` Network + JSON.parse (avg): ~${(e2eStats.avg - serverStats.avg).toFixed(2)}ms`, + ); + } }); - expect(result.checkout_url).toBeDefined(); - expect(result.checkout_url).toContain("checkout.stripe.com"); + const entitlementCounts = [1, 5, 10, 20]; + + for (const count of entitlementCounts) { + test(`benchmark deduction WITH path index — ${count} entitlement(s)`, async () => { + // Re-seed to reset balances + const fullCustomer = buildLargeFullCustomer(); + const pathIndexEntries = buildPathIndex({ fullCustomer }); + await redis.setFullCustomerCache( + cacheKey, + ORG_ID, + ENV, + CUSTOMER_ID, + String(Date.now()), + String(FULL_CUSTOMER_CACHE_TTL_SECONDS), + JSON.stringify(fullCustomer), + "true", + JSON.stringify(pathIndexEntries), + ); + + const exists = await redis.call("EXISTS", pathIdxKey); + expect(exists).toBe(1); + + const { e2eStats, serverStats } = await runSlowlogBenchmark({ + iterations: ITERATIONS, + label: chalk.green(`Fast path — ${count} entitlement(s)`), + entitlementCount: count, + }); + + (globalThis as Record)[`__fastPathStats_${count}`] = { + e2eStats, + serverStats, + }; + }); + } - await completeStripeCheckoutForm({ - url: result.checkout_url, - // overrideQuantity: checkoutQuantityInUnits / 100, + for (const count of entitlementCounts) { + test(`benchmark deduction WITHOUT path index — ${count} entitlement(s)`, async () => { + // Re-seed to reset balances, then delete path index + const fullCustomer = buildLargeFullCustomer(); + const pathIndexEntries = buildPathIndex({ fullCustomer }); + await redis.setFullCustomerCache( + cacheKey, + ORG_ID, + ENV, + CUSTOMER_ID, + String(Date.now()), + String(FULL_CUSTOMER_CACHE_TTL_SECONDS), + JSON.stringify(fullCustomer), + "true", + JSON.stringify(pathIndexEntries), + ); + await redis.call("DEL", pathIdxKey); + + const { e2eStats, serverStats } = await runSlowlogBenchmark({ + iterations: ITERATIONS, + label: chalk.red(`Fallback — ${count} entitlement(s)`), + entitlementCount: count, + }); + + (globalThis as Record)[`__fallbackStats_${count}`] = { + e2eStats, + serverStats, + }; + }); + } + + test("comparison table", async () => { + // Re-seed for subsequent runs + const fullCustomer = buildLargeFullCustomer(); + const pathIndexEntries = buildPathIndex({ fullCustomer }); + await redis.setFullCustomerCache( + cacheKey, + ORG_ID, + ENV, + CUSTOMER_ID, + String(Date.now()), + String(FULL_CUSTOMER_CACHE_TTL_SECONDS), + JSON.stringify(fullCustomer), + "true", + JSON.stringify(pathIndexEntries), + ); + + type Stats = { + e2eStats: ReturnType; + serverStats: ReturnType | null; + }; + const g = globalThis as Record; + + console.log(chalk.bold("\n ─── Comparison: Fast path vs Fallback ───")); + console.log(" │ Ents │ Fast (server) │ Fallback (server) │ Speedup │"); + console.log(" │──────│───────────────│───────────────────│─────────│"); + + for (const count of entitlementCounts) { + const fast = g[`__fastPathStats_${count}`] as Stats | undefined; + const slow = g[`__fallbackStats_${count}`] as Stats | undefined; + if (fast?.serverStats && slow?.serverStats) { + const speedup = slow.serverStats.avg / fast.serverStats.avg; + console.log( + ` │ ${String(count).padStart(4)} │ ${fast.serverStats.avg.toFixed(2).padStart(9)}ms │ ${slow.serverStats.avg.toFixed(2).padStart(13)}ms │ ${speedup.toFixed(1).padStart(5)}x │`, + ); + } + } }); - await timeout(12000); + test("benchmark setFullCustomerCache", async () => { + const fullCustomer = buildLargeFullCustomer(); + const serialized = JSON.stringify(fullCustomer); + const pathIndexEntries = buildPathIndex({ fullCustomer }); + const pathIndexJson = JSON.stringify(pathIndexEntries); + + await redis.call("CONFIG", "SET", "slowlog-log-slower-than", "0"); + await redis.call("CONFIG", "SET", "slowlog-max-len", "256"); + await redis.call("SLOWLOG", "RESET"); + + const timings: number[] = []; + for (let i = 0; i < ITERATIONS; i++) { + const start = performance.now(); + const result = await redis.setFullCustomerCache( + cacheKey, + ORG_ID, + ENV, + CUSTOMER_ID, + String(Date.now()), + String(FULL_CUSTOMER_CACHE_TTL_SECONDS), + serialized, + "true", + pathIndexJson, + ); + const elapsed = performance.now() - start; + timings.push(elapsed); + expect(result).toBe("OK"); + } + + const rawEntries = (await redis.call( + "SLOWLOG", + "GET", + "256", + )) as SlowlogEntry[]; + const evalEntries = rawEntries.filter( + (entry) => + entry[3] && (entry[3][0] === "evalsha" || entry[3][0] === "EVALSHA"), + ); + const serverTimings = evalEntries.map((entry) => entry[2] / 1000); + + await redis.call("CONFIG", "SET", "slowlog-log-slower-than", "10000"); + + const e2eStats = formatStats(timings); + const serverStats = + serverTimings.length > 0 ? formatStats(serverTimings) : null; + + console.log( + chalk.magenta(`\n setFullCustomerCache (${ITERATIONS} iterations):`), + ); + printStats("End-to-end (TS)", e2eStats); + if (serverStats) { + printStats("Server-side (SLOWLOG)", serverStats); + } + }); }); From 011e08f264a881e50767ef1415da0b13e4bc8e39 Mon Sep 17 00:00:00 2001 From: John Yeo Date: Tue, 24 Mar 2026 13:48:07 +0000 Subject: [PATCH 46/49] fix: has path idx bug in spendLimitUtils.lua --- .../deductFromCustomerEntitlements/spendLimitUtils.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/_luaScriptsV2/deductFromCustomerEntitlements/spendLimitUtils.lua b/server/src/_luaScriptsV2/deductFromCustomerEntitlements/spendLimitUtils.lua index ace4031d3..e43aec44c 100644 --- a/server/src/_luaScriptsV2/deductFromCustomerEntitlements/spendLimitUtils.lua +++ b/server/src/_luaScriptsV2/deductFromCustomerEntitlements/spendLimitUtils.lua @@ -14,7 +14,7 @@ local function build_ent_data_from_full_customer(params) end -- Fast path: single JSON.GET on the sub-object via path index - if not is_nil(pathidx_key) then + if params.has_pathidx then local result = get_customer_entitlement_via_index({ pathidx_key = pathidx_key, cache_key = cache_key, @@ -79,6 +79,7 @@ local function get_available_overage_from_spend_limit(params) cus_ent_id = cus_ent_id, cache_key = context.cache_key, pathidx_key = context.pathidx_key, + has_pathidx = context.has_pathidx, }) end From 5e682347f9b3c43db64b299d9641383ef950df50 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:11:00 +0000 Subject: [PATCH 47/49] =?UTF-8?q?fix:=20=F0=9F=90=9B=20test=20broken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../track/usage-alerts/usage-alerts-credit-system.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/tests/integration/balances/track/usage-alerts/usage-alerts-credit-system.test.ts b/server/tests/integration/balances/track/usage-alerts/usage-alerts-credit-system.test.ts index 04436c9cd..14fbea0b5 100644 --- a/server/tests/integration/balances/track/usage-alerts/usage-alerts-credit-system.test.ts +++ b/server/tests/integration/balances/track/usage-alerts/usage-alerts-credit-system.test.ts @@ -62,7 +62,7 @@ test(`${chalk.yellowBright("usage-alert credit system: track action1 triggers al const { customerId, autumnV2_1 } = await initScenario({ customerId: "usage-alert-credit-action1", setup: [ - s.customer({ testClock: false }), + s.customer({ testClock: false, paymentMethod: "success" }), s.products({ list: [creditSystemProduct] }), ], actions: [s.attach({ productId: creditSystemProduct.id })], @@ -107,7 +107,7 @@ test(`${chalk.yellowBright("usage-alert credit system: track action2 triggers al const { customerId, autumnV2_1 } = await initScenario({ customerId: "usage-alert-credit-action2", setup: [ - s.customer({ testClock: false }), + s.customer({ testClock: false, paymentMethod: "success" }), s.products({ list: [creditSystemProduct] }), ], actions: [s.attach({ productId: creditSystemProduct.id })], From 0fde605077e7346d3f1e5fa7677afce98855a81b Mon Sep 17 00:00:00 2001 From: John Yeo Date: Tue, 24 Mar 2026 19:54:19 +0000 Subject: [PATCH 48/49] fix: batch delete customers --- .../batchDeleteFullCustomerCache.lua | 46 ------ server/src/_luaScriptsV2/luaScriptsV2.ts | 9 -- server/src/external/redis/initRedis.ts | 11 -- .../batchDeleteCachedFullCustomers.ts | 153 +++++++++++++----- .../_temp/batchDeleteCachedCustomers.test.ts | 87 ++++++++++ 5 files changed, 197 insertions(+), 109 deletions(-) delete mode 100644 server/src/_luaScriptsV2/deleteFullCustomerCache/batchDeleteFullCustomerCache.lua create mode 100644 server/tests/_temp/batchDeleteCachedCustomers.test.ts diff --git a/server/src/_luaScriptsV2/deleteFullCustomerCache/batchDeleteFullCustomerCache.lua b/server/src/_luaScriptsV2/deleteFullCustomerCache/batchDeleteFullCustomerCache.lua deleted file mode 100644 index 5dba9975d..000000000 --- a/server/src/_luaScriptsV2/deleteFullCustomerCache/batchDeleteFullCustomerCache.lua +++ /dev/null @@ -1,46 +0,0 @@ ---[[ - Batch delete multiple FullCustomer caches from Redis. - - For each customer, atomically: - 1. Checks if test guard exists (skip that customer if so) - 2. Sets the stale-write guard key - 3. Deletes the cache key - - KEYS: none (all keys passed via ARGV to support variable number of customers) - - ARGV: - [1] guardTimestamp - timestamp for all guards - [2] guardTtl - TTL in seconds for guard keys - [3] customersJson - JSON array of {testGuardKey, guardKey, cacheKey} objects - - Returns: - JSON object: { deleted: number, skipped: number } -]] - -local guardTimestamp = ARGV[1] -local guardTtl = tonumber(ARGV[2]) -local customersJson = ARGV[3] - -local customers = cjson.decode(customersJson) -local deleted = 0 -local skipped = 0 - -for _, customer in ipairs(customers) do - local testGuardKey = customer.testGuardKey - local guardKey = customer.guardKey - local cacheKey = customer.cacheKey - - -- Check test guard first - if redis.call("EXISTS", testGuardKey) == 1 then - skipped = skipped + 1 - else - -- Set stale-write guard and delete cache - redis.call("SET", guardKey, guardTimestamp, "EX", guardTtl) - local wasDeleted = redis.call("DEL", cacheKey) - if wasDeleted > 0 then - deleted = deleted + 1 - end - end -end - -return cjson.encode({ deleted = deleted, skipped = skipped }) diff --git a/server/src/_luaScriptsV2/luaScriptsV2.ts b/server/src/_luaScriptsV2/luaScriptsV2.ts index 0bb431aa5..a31e4d5f6 100644 --- a/server/src/_luaScriptsV2/luaScriptsV2.ts +++ b/server/src/_luaScriptsV2/luaScriptsV2.ts @@ -165,15 +165,6 @@ const setFullCustomerCacheScript = readFileSync( export const SET_FULL_CUSTOMER_CACHE_SCRIPT = `${FULL_CUSTOMER_KEY_BUILDERS} ${setFullCustomerCacheScript}`; -/** - * Lua script for batch deleting multiple FullCustomer caches from Redis. - * For each customer: checks test guard, sets stale-write guard, deletes cache. - */ -export const BATCH_DELETE_FULL_CUSTOMER_CACHE_SCRIPT = readFileSync( - join(DELETE_CACHE_DIR, "batchDeleteFullCustomerCache.lua"), - "utf-8", -); - // ============================================================================ // RESET CUSTOMER ENTITLEMENTS SCRIPT (deprecated — kept for backward compat) // ============================================================================ diff --git a/server/src/external/redis/initRedis.ts b/server/src/external/redis/initRedis.ts index 3962fd001..d90a2f447 100644 --- a/server/src/external/redis/initRedis.ts +++ b/server/src/external/redis/initRedis.ts @@ -16,7 +16,6 @@ import { import { ADJUST_CUSTOMER_ENTITLEMENT_BALANCE_SCRIPT, APPEND_ENTITY_TO_CUSTOMER_SCRIPT, - BATCH_DELETE_FULL_CUSTOMER_CACHE_SCRIPT, CLAIM_LOCK_RECEIPT_SCRIPT, DEDUCT_FROM_CUSTOMER_ENTITLEMENTS_SCRIPT, DELETE_FULL_CUSTOMER_CACHE_SCRIPT, @@ -179,11 +178,6 @@ const configureRedisInstance = (redisInstance: Redis): Redis => { lua: DELETE_FULL_CUSTOMER_CACHE_SCRIPT, }); - redisInstance.defineCommand("batchDeleteFullCustomerCache", { - numberOfKeys: 0, - lua: BATCH_DELETE_FULL_CUSTOMER_CACHE_SCRIPT, - }); - redisInstance.defineCommand("setFullCustomerCache", { numberOfKeys: 1, lua: SET_FULL_CUSTOMER_CACHE_SCRIPT, @@ -417,11 +411,6 @@ declare module "ioredis" { guardTtl: string, skipGuard: string, ): Promise<"SKIPPED" | "DELETED" | "NOT_FOUND">; - batchDeleteFullCustomerCache( - guardTimestamp: string, - guardTtl: string, - customersJson: string, - ): Promise; setFullCustomerCache( cacheKey: string, orgId: string, diff --git a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/batchDeleteCachedFullCustomers.ts b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/batchDeleteCachedFullCustomers.ts index 86565b3b8..a05bc8e7c 100644 --- a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/batchDeleteCachedFullCustomers.ts +++ b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/batchDeleteCachedFullCustomers.ts @@ -1,11 +1,10 @@ -import { - type Logger, - logger as loggerInstance, -} from "@/external/logtail/logtailUtils.js"; +import type { Redis } from "ioredis"; +import type { Logger } from "@/external/logtail/logtailUtils.js"; import { getConfiguredRegions, getRegionalRedis, } from "@/external/redis/initRedis.js"; +import { buildPathIndexKey } from "@/internal/customers/cache/pathIndex/pathIndexConfig.js"; import { buildFullCustomerCacheGuardKey, buildFullCustomerCacheKey, @@ -19,21 +18,111 @@ type CustomerToDelete = { customerId: string; }; +const isProductionNode = process.env.NODE_ENV === "production"; + +/** + * Per org: all keys share `{orgId}` so Redis Cluster stays in one slot per pipeline. + */ +const deleteFullCustomerCacheRowsForOrg = async ({ + regionalRedis, + orgCustomers, + guardTimestamp, +}: { + regionalRedis: Redis; + orgCustomers: CustomerToDelete[]; + guardTimestamp: string; +}): Promise<{ deleted: number; skipped: number }> => { + let skipped = 0; + let customersToProcess = orgCustomers; + + if (!isProductionNode) { + const existsPipeline = regionalRedis.pipeline(); + for (const customer of orgCustomers) { + existsPipeline.exists( + buildTestFullCustomerCacheGuardKey({ + orgId: customer.orgId, + env: customer.env, + customerId: customer.customerId, + }), + ); + } + const existsResults = await existsPipeline.exec(); + if (!existsResults) return { deleted: 0, skipped: 0 }; + + const allowed: CustomerToDelete[] = []; + for (let index = 0; index < orgCustomers.length; index++) { + const tuple = existsResults[index]; + if (!tuple) + throw new Error( + "batchDeleteCachedFullCustomers: missing EXISTS result", + ); + const [error, existsCount] = tuple; + if (error) throw error; + if (existsCount === 1) { + skipped += 1; + continue; + } + allowed.push(orgCustomers[index]!); + } + customersToProcess = allowed; + } + + if (customersToProcess.length === 0) return { deleted: 0, skipped }; + + const pipeline = regionalRedis.pipeline(); + for (const customer of customersToProcess) { + const { orgId, env, customerId } = customer; + const guardKey = buildFullCustomerCacheGuardKey({ orgId, env, customerId }); + const cacheKey = buildFullCustomerCacheKey({ orgId, env, customerId }); + const pathIndexKey = buildPathIndexKey({ orgId, env, customerId }); + pipeline.set( + guardKey, + guardTimestamp, + "EX", + FULL_CUSTOMER_CACHE_GUARD_TTL_SECONDS, + ); + pipeline.unlink(cacheKey); + pipeline.unlink(pathIndexKey); + } + + const deleteResults = await pipeline.exec(); + if (!deleteResults) return { deleted: 0, skipped }; + + let deleted = 0; + for ( + let customerIndex = 0; + customerIndex < customersToProcess.length; + customerIndex++ + ) { + const baseIndex = customerIndex * 3; + for (let commandOffset = 0; commandOffset < 3; commandOffset++) { + const tuple = deleteResults[baseIndex + commandOffset]; + if (!tuple) + throw new Error( + "batchDeleteCachedFullCustomers: missing pipeline result", + ); + const [error] = tuple; + if (error) throw error; + } + const unlinkCacheTuple = deleteResults[baseIndex + 1]; + const unlinkCount = unlinkCacheTuple![1] as number; + if (unlinkCount > 0) deleted += 1; + } + + return { deleted, skipped }; +}; + /** * Batch delete multiple FullCustomer caches across ALL regions. */ export const batchDeleteCachedFullCustomers = async ({ customers, - logger, }: { customers: CustomerToDelete[]; logger?: Logger; }): Promise => { - const log = logger || loggerInstance; - if (customers.length === 0) return 0; - // Group customers by orgId for Redis Cluster slot consistency const customersByOrg = new Map(); for (const customer of customers) { const existing = customersByOrg.get(customer.orgId) || []; @@ -44,22 +133,6 @@ export const batchDeleteCachedFullCustomers = async ({ const regions = getConfiguredRegions(); const guardTimestamp = Date.now().toString(); - // Build customers data once (shared across all regions) - const customersDataByOrg = new Map(); - for (const [orgId, orgCustomers] of customersByOrg) { - const customersData = orgCustomers.map(({ env, customerId }) => ({ - testGuardKey: buildTestFullCustomerCacheGuardKey({ - orgId, - env, - customerId, - }), - guardKey: buildFullCustomerCacheGuardKey({ orgId, env, customerId }), - cacheKey: buildFullCustomerCacheKey({ orgId, env, customerId }), - })); - customersDataByOrg.set(orgId, customersData); - } - - // Delete from all regions in parallel const regionPromises = regions.map(async (region) => { const regionalRedis = getRegionalRedis(region); @@ -68,32 +141,26 @@ export const batchDeleteCachedFullCustomers = async ({ return 0; } - const pipeline = regionalRedis.pipeline(); - for (const customersData of customersDataByOrg.values()) { - pipeline.batchDeleteFullCustomerCache( - guardTimestamp, - FULL_CUSTOMER_CACHE_GUARD_TTL_SECONDS.toString(), - JSON.stringify(customersData), - ); - } - - const results = await pipeline.exec(); let deleted = 0; - - if (results) { - for (const [error, resultJson] of results) { - if (error) throw error; - const result = JSON.parse(resultJson as string) as { deleted: number }; - deleted += result.deleted; - } + let skipped = 0; + for (const orgCustomers of customersByOrg.values()) { + const orgResult = await deleteFullCustomerCacheRowsForOrg({ + regionalRedis, + orgCustomers, + guardTimestamp, + }); + deleted += orgResult.deleted; + skipped += orgResult.skipped; } + const skipSuffix = + !isProductionNode && skipped > 0 ? `, skipped_test_guard ${skipped}` : ""; console.info( - `[batchDeleteCachedFullCustomers] ${region}: deleted ${deleted} keys, customers (${customers.length}), orgs (${customersByOrg.size})`, + `[batchDeleteCachedFullCustomers] ${region}: unlinked ${deleted} cache keys, customers (${customers.length}), orgs (${customersByOrg.size})${skipSuffix}`, ); return deleted; }); const regionDeleted = await Promise.all(regionPromises); - return regionDeleted.reduce((sum, d) => sum + d, 0); + return regionDeleted.reduce((sum, count) => sum + count, 0); }; diff --git a/server/tests/_temp/batchDeleteCachedCustomers.test.ts b/server/tests/_temp/batchDeleteCachedCustomers.test.ts new file mode 100644 index 000000000..914378987 --- /dev/null +++ b/server/tests/_temp/batchDeleteCachedCustomers.test.ts @@ -0,0 +1,87 @@ +import { expect, test } from "bun:test"; +import type { ApiCustomer } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { redis } from "@/external/redis/initRedis.js"; +import { buildPathIndexKey } from "@/internal/customers/cache/pathIndex/pathIndexConfig.js"; +import { batchDeleteCachedCustomers } from "@/internal/customers/cusUtils/apiCusCacheUtils/batchDeleteCachedCustomers.js"; +import { buildFullCustomerCacheKey } from "@/internal/customers/cusUtils/fullCustomerCacheUtils/fullCustomerCacheConfig.js"; + +test.concurrent(`${chalk.yellowBright("batchDeleteCachedCustomers: clears full customer cache + path index after V2 get")}`, async () => { + test.skipIf(redis.status !== "ready"); + + const messagesItem = items.monthlyMessages({ includedUsage: 50 }); + const freeProd = products.base({ id: "free", items: [messagesItem] }); + const otherCustomerLabel = "batch-del-cache-other"; + + const { customerId, autumnV2, ctx, otherCustomers } = await initScenario({ + customerId: "batch-del-cache-primary", + setup: [ + s.customer({ testClock: false }), + s.products({ list: [freeProd] }), + s.otherCustomers([{ id: otherCustomerLabel, paymentMethod: "success" }]), + ], + actions: [ + s.attach({ productId: freeProd.id }), + s.attach({ + productId: freeProd.id, + customerId: otherCustomerLabel, + }), + ], + }); + + const otherEntry = otherCustomers.get(otherCustomerLabel); + if (!otherEntry) throw new Error("other customer not initialized"); + + await autumnV2.customers.get(customerId); + await autumnV2.customers.get(otherEntry.id); + + const orgId = ctx.org.id; + const env = ctx.env; + + const primaryFullKey = buildFullCustomerCacheKey({ + orgId, + env, + customerId, + }); + const otherFullKey = buildFullCustomerCacheKey({ + orgId, + env, + customerId: otherEntry.id, + }); + const primaryPathKey = buildPathIndexKey({ + orgId, + env, + customerId, + }); + const otherPathKey = buildPathIndexKey({ + orgId, + env, + customerId: otherEntry.id, + }); + + expect(await redis.call("EXISTS", primaryFullKey)).toBe(1); + expect(await redis.call("EXISTS", otherFullKey)).toBe(1); + expect(await redis.call("EXISTS", primaryPathKey)).toBe(1); + expect(await redis.call("EXISTS", otherPathKey)).toBe(1); + + await batchDeleteCachedCustomers({ + customers: [ + { orgId, env, customerId }, + { orgId, env, customerId: otherEntry.id }, + ], + }); + + expect(await redis.call("EXISTS", primaryFullKey)).toBe(0); + expect(await redis.call("EXISTS", otherFullKey)).toBe(0); + expect(await redis.call("EXISTS", primaryPathKey)).toBe(0); + expect(await redis.call("EXISTS", otherPathKey)).toBe(0); + + const primaryFromDb = await autumnV2.customers.get(customerId, { + skip_cache: "true", + }); + expect(primaryFromDb.balances[TestFeature.Messages]).toBeDefined(); +}); From d14a15942461a81da084b62c84cfd01c6923f466 Mon Sep 17 00:00:00 2001 From: jjmaxwell4 Date: Tue, 24 Mar 2026 13:11:43 -0700 Subject: [PATCH 49/49] chore: serve OpenAPI spec at /openapi.yml and /openapi.json Made-with: Cursor --- bun.lock | 1 + server/package.json | 1 + server/src/initHono.ts | 16 ++++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/bun.lock b/bun.lock index 4c5c1fec2..91ae1829a 100644 --- a/bun.lock +++ b/bun.lock @@ -365,6 +365,7 @@ "svix": "^1.45.1", "tsc-alias": "^1.8.16", "ws": "^8.18.0", + "yaml": "^2.8.3", "zod": "^3.25.23", }, "devDependencies": { diff --git a/server/package.json b/server/package.json index 77a05ffe2..faae034d1 100644 --- a/server/package.json +++ b/server/package.json @@ -130,6 +130,7 @@ "svix": "^1.45.1", "tsc-alias": "^1.8.16", "ws": "^8.18.0", + "yaml": "^2.8.3", "zod": "^3.25.23" }, "devDependencies": { diff --git a/server/src/initHono.ts b/server/src/initHono.ts index 3f6dcf927..7c794cb01 100644 --- a/server/src/initHono.ts +++ b/server/src/initHono.ts @@ -1,3 +1,5 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; import { oauthClient } from "@autumn/shared"; import { oauthProviderAuthServerMetadata, @@ -7,6 +9,7 @@ import { httpInstrumentationMiddleware } from "@hono/otel"; import { eq } from "drizzle-orm"; import { Hono } from "hono"; import { cors } from "hono/cors"; +import { parse as parseYaml } from "yaml"; import { autumnWebhookRouter } from "./external/autumn/autumnWebhookRouter.js"; import { revenuecatWebhookRouter } from "./external/revenueCat/revenuecatWebhookRouter.js"; import { stripeWebhookRouter } from "./external/stripe/stripeWebhookRouter.js"; @@ -24,6 +27,10 @@ import { publicRouter } from "./routers/publicRouter.js"; import { auth } from "./utils/auth.js"; import { isAllowedOrigin } from "./utils/corsOrigins.js"; +const openapiPath = resolve(import.meta.dir, "../../packages/openapi/openapi.yml"); +const openapiYaml = readFileSync(openapiPath, "utf-8"); +const openapiJson = JSON.stringify(parseYaml(openapiYaml)); + const ALLOWED_HEADERS = [ "app_env", "x-api-version", @@ -94,6 +101,15 @@ export const createHonoApp = () => { app.get("/", handleHealthCheck); + app.get("/openapi.yml", (c) => { + c.header("Content-Type", "text/yaml"); + return c.body(openapiYaml); + }); + + app.get("/openapi.json", (c) => { + return c.json(JSON.parse(openapiJson)); + }); + // Public endpoint to get OAuth client name (for consent page) app.get("/oauth/client/:client_id", async (c) => { const clientId = c.req.param("client_id");