diff --git a/.gitignore b/.gitignore
index ea19efba48..8ddc5610a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -157,6 +157,7 @@ server/.turbo
notes.txt
server/experiments/results/*
!server/experiments/results/.gitkeep
+server/experiments/out/getFullSubject-*
# Leaf contract fixtures are synced from private S3.
apps/leaf/contracts/
diff --git a/ai b/ai
index bce748efa3..ae59fe577c 160000
--- a/ai
+++ b/ai
@@ -1 +1 @@
-Subproject commit bce748efa378d055b018b0f59251f44ecd8e06c4
+Subproject commit ae59fe577cb20936b38e2b853c961e7753bd30b6
diff --git a/apps/docs/mintlify/api-reference/billing/attach.mdx b/apps/docs/mintlify/api-reference/billing/attach.mdx
index b9e63ab078..4ac976756f 100644
--- a/apps/docs/mintlify/api-reference/billing/attach.mdx
+++ b/apps/docs/mintlify/api-reference/billing/attach.mdx
@@ -506,7 +506,7 @@ A customer who has paid is locked to their currency: passing a different one, or
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -520,6 +520,10 @@ A customer who has paid is locked to their currency: passing a different one, or
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/billing/billingUpdate.mdx b/apps/docs/mintlify/api-reference/billing/billingUpdate.mdx
index 7993df4138..6339625538 100644
--- a/apps/docs/mintlify/api-reference/billing/billingUpdate.mdx
+++ b/apps/docs/mintlify/api-reference/billing/billingUpdate.mdx
@@ -467,7 +467,7 @@ const response = await autumn.billing.update({
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -481,6 +481,10 @@ const response = await autumn.billing.update({
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/billing/createSchedule.mdx b/apps/docs/mintlify/api-reference/billing/createSchedule.mdx
index 8d7ca36efd..3d02e5cdc0 100644
--- a/apps/docs/mintlify/api-reference/billing/createSchedule.mdx
+++ b/apps/docs/mintlify/api-reference/billing/createSchedule.mdx
@@ -459,7 +459,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -473,6 +473,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/billing/import.mdx b/apps/docs/mintlify/api-reference/billing/import.mdx
index 3520c0bebe..4acf8559d6 100644
--- a/apps/docs/mintlify/api-reference/billing/import.mdx
+++ b/apps/docs/mintlify/api-reference/billing/import.mdx
@@ -982,7 +982,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -996,6 +996,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1917,7 +1921,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1931,6 +1935,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/billing/multiAttach.mdx b/apps/docs/mintlify/api-reference/billing/multiAttach.mdx
index a6309cbbab..cb9a728718 100644
--- a/apps/docs/mintlify/api-reference/billing/multiAttach.mdx
+++ b/apps/docs/mintlify/api-reference/billing/multiAttach.mdx
@@ -377,7 +377,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -391,6 +391,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/billing/previewAttach.mdx b/apps/docs/mintlify/api-reference/billing/previewAttach.mdx
index d8de8d897b..33de9a9ab8 100644
--- a/apps/docs/mintlify/api-reference/billing/previewAttach.mdx
+++ b/apps/docs/mintlify/api-reference/billing/previewAttach.mdx
@@ -433,7 +433,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -447,6 +447,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1687,7 +1691,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1701,6 +1705,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -2600,7 +2608,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -2614,6 +2622,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/billing/previewMultiAttach.mdx b/apps/docs/mintlify/api-reference/billing/previewMultiAttach.mdx
index 295ea5f2d4..be17f6b5db 100644
--- a/apps/docs/mintlify/api-reference/billing/previewMultiAttach.mdx
+++ b/apps/docs/mintlify/api-reference/billing/previewMultiAttach.mdx
@@ -377,7 +377,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -391,6 +391,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1395,7 +1399,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1409,6 +1413,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -2308,7 +2316,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -2322,6 +2330,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/billing/previewMultiUpdate.mdx b/apps/docs/mintlify/api-reference/billing/previewMultiUpdate.mdx
index c735a08078..a191ff6e26 100644
--- a/apps/docs/mintlify/api-reference/billing/previewMultiUpdate.mdx
+++ b/apps/docs/mintlify/api-reference/billing/previewMultiUpdate.mdx
@@ -822,7 +822,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -836,6 +836,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1735,7 +1739,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1749,6 +1753,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/billing/previewUpdate.mdx b/apps/docs/mintlify/api-reference/billing/previewUpdate.mdx
index 5d8231c9ca..995a75ccd2 100644
--- a/apps/docs/mintlify/api-reference/billing/previewUpdate.mdx
+++ b/apps/docs/mintlify/api-reference/billing/previewUpdate.mdx
@@ -433,7 +433,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -447,6 +447,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1630,7 +1634,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1644,6 +1648,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -2543,7 +2551,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -2557,6 +2565,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/billing/setupPayment.mdx b/apps/docs/mintlify/api-reference/billing/setupPayment.mdx
index 263b7b763f..925b8aa096 100644
--- a/apps/docs/mintlify/api-reference/billing/setupPayment.mdx
+++ b/apps/docs/mintlify/api-reference/billing/setupPayment.mdx
@@ -433,7 +433,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -447,6 +447,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/customers/getCustomer.mdx b/apps/docs/mintlify/api-reference/customers/getCustomer.mdx
index 53451fc65a..1d3567eab6 100644
--- a/apps/docs/mintlify/api-reference/customers/getCustomer.mdx
+++ b/apps/docs/mintlify/api-reference/customers/getCustomer.mdx
@@ -801,7 +801,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -815,6 +815,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1736,7 +1740,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1750,6 +1754,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/customers/getOrCreateCustomer.mdx b/apps/docs/mintlify/api-reference/customers/getOrCreateCustomer.mdx
index 48cb5d9af9..143d96ea4c 100644
--- a/apps/docs/mintlify/api-reference/customers/getOrCreateCustomer.mdx
+++ b/apps/docs/mintlify/api-reference/customers/getOrCreateCustomer.mdx
@@ -80,7 +80,7 @@ Pass `currency` to bill this customer in a currency other than your organization
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -94,6 +94,10 @@ Pass `currency` to bill this customer in a currency other than your organization
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1005,7 +1009,7 @@ Pass `currency` to bill this customer in a currency other than your organization
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1019,6 +1023,10 @@ Pass `currency` to bill this customer in a currency other than your organization
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1940,7 +1948,7 @@ Pass `currency` to bill this customer in a currency other than your organization
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1954,6 +1962,10 @@ Pass `currency` to bill this customer in a currency other than your organization
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/customers/listCustomers.mdx b/apps/docs/mintlify/api-reference/customers/listCustomers.mdx
index 98e5d65318..bb1ab2543c 100644
--- a/apps/docs/mintlify/api-reference/customers/listCustomers.mdx
+++ b/apps/docs/mintlify/api-reference/customers/listCustomers.mdx
@@ -826,7 +826,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -840,6 +840,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1761,7 +1765,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1775,6 +1779,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/customers/updateCustomer.mdx b/apps/docs/mintlify/api-reference/customers/updateCustomer.mdx
index 57248c330c..e099aa62ef 100644
--- a/apps/docs/mintlify/api-reference/customers/updateCustomer.mdx
+++ b/apps/docs/mintlify/api-reference/customers/updateCustomer.mdx
@@ -64,7 +64,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -78,6 +78,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -989,7 +993,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1003,6 +1007,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1924,7 +1932,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1938,6 +1946,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/entities/createEntity.mdx b/apps/docs/mintlify/api-reference/entities/createEntity.mdx
index 8ba9db9165..e03d6b0939 100644
--- a/apps/docs/mintlify/api-reference/entities/createEntity.mdx
+++ b/apps/docs/mintlify/api-reference/entities/createEntity.mdx
@@ -181,7 +181,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -195,6 +195,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -922,7 +926,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -936,6 +940,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1856,7 +1864,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1870,6 +1878,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/entities/getEntity.mdx b/apps/docs/mintlify/api-reference/entities/getEntity.mdx
index e7aef63475..030dd111cf 100644
--- a/apps/docs/mintlify/api-reference/entities/getEntity.mdx
+++ b/apps/docs/mintlify/api-reference/entities/getEntity.mdx
@@ -610,7 +610,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -624,6 +624,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1544,7 +1548,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1558,6 +1562,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/entities/listEntities.mdx b/apps/docs/mintlify/api-reference/entities/listEntities.mdx
index 616a153f3a..4a5c6e2648 100644
--- a/apps/docs/mintlify/api-reference/entities/listEntities.mdx
+++ b/apps/docs/mintlify/api-reference/entities/listEntities.mdx
@@ -639,7 +639,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -653,6 +653,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1573,7 +1577,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1587,6 +1591,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/entities/updateEntity.mdx b/apps/docs/mintlify/api-reference/entities/updateEntity.mdx
index a2dffea73f..75b1135483 100644
--- a/apps/docs/mintlify/api-reference/entities/updateEntity.mdx
+++ b/apps/docs/mintlify/api-reference/entities/updateEntity.mdx
@@ -712,7 +712,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -726,6 +726,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1646,7 +1650,7 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1660,6 +1664,10 @@ import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/invoices/insertInvoices.mdx b/apps/docs/mintlify/api-reference/invoices/insertInvoices.mdx
new file mode 100644
index 0000000000..608a0756cf
--- /dev/null
+++ b/apps/docs/mintlify/api-reference/invoices/insertInvoices.mdx
@@ -0,0 +1,143 @@
+---
+title: "Insert Invoices"
+openapi: "openapi POST /v1/invoices.insert"
+---
+
+import { DynamicParamField } from "/snippets/dynamic-param-field.jsx";
+import { DynamicResponseField } from "/snippets/dynamic-response-field.jsx";
+import { DynamicResponseExample } from "/snippets/dynamic-response-example.jsx";
+
+### Body Parameters
+
+
+ Invoices to insert or update, in response order.
+
+
+ The customer this invoice belongs to.
+
+
+
+ Plan IDs represented by this invoice.
+
+
+
+ The processor's stable invoice ID.
+
+
+
+ The billing processor that owns this invoice.
+
+
+
+ The invoice status.
+
+
+
+ The invoice total in major currency units.
+
+
+
+ The amount paid in major currency units.
+
+
+
+ The refunded amount in major currency units.
+
+
+
+ The currency code. Defaults to the organization's default currency.
+
+
+
+ The invoice creation timestamp in milliseconds.
+
+
+
+ The hosted invoice URL, when available.
+
+
+
+
+
+
+### Response
+
+
+ Inserted or updated invoices in request order.
+
+
+ Array of plan IDs included in this invoice
+
+
+
+ The Stripe invoice ID
+
+
+
+ The billing processor that owns this invoice.
+
+
+
+ The status of the invoice
+
+
+
+ The total amount of the invoice
+
+
+
+ The currency code for the invoice
+
+
+
+ Timestamp when the invoice was created
+
+
+
+ URL to the Stripe-hosted invoice page
+
+
+
+ The Autumn invoice ID.
+
+
+
+ The customer this invoice belongs to.
+
+
+
+ The amount paid in major currency units.
+
+
+
+ The refunded amount in major currency units.
+
+
+
+
+
+
+
+```json 200
+{
+ "invoices": [
+ {
+ "id": "inv_2b3c4d5e6f7g8h",
+ "customer_id": "cus_123",
+ "plan_ids": [
+ "pro"
+ ],
+ "stripe_id": "in_legacy_123",
+ "processor_type": "stripe",
+ "status": "paid",
+ "total": 29.99,
+ "amount_paid": 29.99,
+ "refunded_amount": 0,
+ "currency": "usd",
+ "created_at": 1451606400000,
+ "hosted_invoice_url": "https://billing.example.com/invoices/legacy-123"
+ }
+ ]
+}
+```
+
diff --git a/apps/docs/mintlify/api-reference/plans/createPlan.mdx b/apps/docs/mintlify/api-reference/plans/createPlan.mdx
index 1e3e2445ec..03386a1515 100644
--- a/apps/docs/mintlify/api-reference/plans/createPlan.mdx
+++ b/apps/docs/mintlify/api-reference/plans/createPlan.mdx
@@ -550,10 +550,6 @@ await autumn.plans.create({
-
- Pin the link to a specific version of the license plan. Omitted, an existing link keeps its pinned version and a new link resolves to the latest.
-
-
@@ -612,7 +608,7 @@ await autumn.plans.create({
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -626,6 +622,10 @@ await autumn.plans.create({
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1301,7 +1301,7 @@ await autumn.plans.create({
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1315,6 +1315,10 @@ await autumn.plans.create({
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/plans/getPlan.mdx b/apps/docs/mintlify/api-reference/plans/getPlan.mdx
index 98c74103ef..9b308af9d0 100644
--- a/apps/docs/mintlify/api-reference/plans/getPlan.mdx
+++ b/apps/docs/mintlify/api-reference/plans/getPlan.mdx
@@ -598,7 +598,7 @@ const plan = await autumn.plans.get({
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -612,6 +612,10 @@ const plan = await autumn.plans.get({
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/plans/listPlans.mdx b/apps/docs/mintlify/api-reference/plans/listPlans.mdx
index 096bd5001b..50aaa84be5 100644
--- a/apps/docs/mintlify/api-reference/plans/listPlans.mdx
+++ b/apps/docs/mintlify/api-reference/plans/listPlans.mdx
@@ -604,7 +604,7 @@ const plans = await autumn.plans.list({
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -618,6 +618,10 @@ const plans = await autumn.plans.list({
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api-reference/plans/updatePlan.mdx b/apps/docs/mintlify/api-reference/plans/updatePlan.mdx
index a519a235e3..8eb8ea8452 100644
--- a/apps/docs/mintlify/api-reference/plans/updatePlan.mdx
+++ b/apps/docs/mintlify/api-reference/plans/updatePlan.mdx
@@ -471,10 +471,6 @@ await autumn.plans.update({
-
- Pin the link to a specific version of the license plan. Omitted, an existing link keeps its pinned version and a new link resolves to the latest.
-
-
@@ -533,7 +529,7 @@ await autumn.plans.update({
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -547,6 +543,10 @@ await autumn.plans.update({
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -697,6 +697,16 @@ await autumn.plans.update({
Variant plan IDs to apply this update to. Empty or omitted means no propagation.
+
+ Parent plan versions that should receive this license-plan update.
+
+
+
+
+
+
+
+
Additive variant updates for this base plan. Missing variants are created when name is provided.
@@ -948,7 +958,7 @@ await autumn.plans.update({
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -962,6 +972,10 @@ await autumn.plans.update({
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
@@ -1659,7 +1673,7 @@ await autumn.plans.update({
- Optional rate limit to cap how often auto top-ups occur.
+ Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
The time interval for the purchase limit window.
@@ -1673,6 +1687,10 @@ await autumn.plans.update({
Maximum number of auto top-ups allowed within the interval.
+
+ Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+
+
diff --git a/apps/docs/mintlify/api/openapi.yml b/apps/docs/mintlify/api/openapi.yml
index 8f7f67983b..36dd960a98 100644
--- a/apps/docs/mintlify/api/openapi.yml
+++ b/apps/docs/mintlify/api/openapi.yml
@@ -76,7 +76,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -103,10 +102,16 @@ components:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -339,7 +344,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -1996,7 +2000,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -2023,10 +2026,16 @@ components:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -2197,7 +2206,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -2831,7 +2839,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -2858,10 +2865,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -3185,7 +3198,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -4261,7 +4273,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -5138,7 +5149,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -5165,10 +5175,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -5404,7 +5420,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -6789,13 +6804,6 @@ paths:
propertyNames:
type: string
additionalProperties: {}
- version:
- type: integer
- minimum: 1
- maximum: 9007199254740991
- description: Pin the link to a specific version of the license plan. Omitted, an
- existing link keeps its pinned version and a new link
- resolves to the latest.
required:
- license_plan_id
description: Plans offered as assignable licenses under this plan. The full set
@@ -6858,7 +6866,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -6885,10 +6892,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -7850,7 +7863,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -7877,10 +7889,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed
+ top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -8054,7 +8073,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -9180,7 +9198,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -9207,10 +9224,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed
+ top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -9384,7 +9408,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -10489,7 +10512,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -10516,10 +10538,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's
+ consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -10694,7 +10723,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -11583,13 +11611,6 @@ paths:
propertyNames:
type: string
additionalProperties: {}
- version:
- type: integer
- minimum: 1
- maximum: 9007199254740991
- description: Pin the link to a specific version of the license plan. Omitted, an
- existing link keeps its pinned version and a new link
- resolves to the latest.
required:
- license_plan_id
description: Plans offered as assignable licenses under this plan. The full set
@@ -11655,7 +11676,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -11682,10 +11702,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -11883,6 +11909,23 @@ paths:
type: string
description: Variant plan IDs to apply this update to. Empty or omitted means no
propagation.
+ update_license_parents:
+ type: array
+ items:
+ type: object
+ properties:
+ plan_id:
+ type: string
+ minLength: 1
+ pattern: ^[a-zA-Z0-9_-]+$
+ version:
+ type: integer
+ minimum: 1
+ maximum: 9007199254740991
+ required:
+ - plan_id
+ - version
+ description: Parent plan versions that should receive this license-plan update.
variants:
type: array
items:
@@ -12238,7 +12281,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -12265,10 +12307,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed
+ top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -13223,7 +13272,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -13250,10 +13298,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed
+ top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -13427,7 +13482,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -15419,7 +15473,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -15446,10 +15499,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -16864,7 +16923,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -16891,10 +16949,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's
+ consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -17565,7 +17630,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -17592,10 +17656,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's
+ consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -19284,7 +19355,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -19311,10 +19381,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -22075,7 +22151,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -22102,10 +22177,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -23437,7 +23518,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -23464,10 +23544,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -25921,7 +26007,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -25948,10 +26033,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -28076,7 +28167,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -28853,7 +28943,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -30642,6 +30731,251 @@ paths:
range="30d",
bin_size="day",
)
+ /v1/invoices.insert:
+ post:
+ operationId: insertInvoices
+ description: Inserts or updates up to 500 historical invoices without reading or
+ mutating the billing processor.
+ tags:
+ - invoices
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ invoices:
+ type: array
+ maxItems: 500
+ items:
+ type: object
+ properties:
+ customer_id:
+ type: string
+ description: The customer this invoice belongs to.
+ plan_ids:
+ type: array
+ items:
+ type: string
+ default: []
+ description: Plan IDs represented by this invoice.
+ stripe_id:
+ type: string
+ description: The processor's stable invoice ID.
+ processor_type:
+ enum:
+ - stripe
+ - revenuecat
+ type: string
+ default: stripe
+ description: The billing processor that owns this invoice.
+ status:
+ enum:
+ - draft
+ - open
+ - void
+ - paid
+ - uncollectible
+ type: string
+ description: The invoice status.
+ total:
+ type: number
+ description: The invoice total in major currency units.
+ amount_paid:
+ anyOf:
+ - type: number
+ - type: "null"
+ default: null
+ description: The amount paid in major currency units.
+ refunded_amount:
+ type: number
+ default: 0
+ description: The refunded amount in major currency units.
+ currency:
+ type: string
+ description: The currency code. Defaults to the organization's default currency.
+ created_at:
+ type: number
+ description: The invoice creation timestamp in milliseconds.
+ hosted_invoice_url:
+ anyOf:
+ - type: string
+ - type: "null"
+ default: null
+ description: The hosted invoice URL, when available.
+ required:
+ - customer_id
+ - stripe_id
+ - status
+ - total
+ - created_at
+ description: Invoices to insert or update, in response order.
+ required:
+ - invoices
+ title: InsertInvoicesParams
+ examples:
+ - &a72
+ invoices:
+ - customer_id: cus_123
+ plan_ids:
+ - pro
+ stripe_id: in_legacy_123
+ processor_type: stripe
+ status: paid
+ total: 29.99
+ amount_paid: 29.99
+ refunded_amount: 0
+ currency: usd
+ created_at: 1451606400000
+ hosted_invoice_url: https://billing.example.com/invoices/legacy-123
+ example: *a72
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ invoices:
+ type: array
+ items:
+ type: object
+ properties:
+ plan_ids:
+ type: array
+ items:
+ type: string
+ description: Array of plan IDs included in this invoice
+ stripe_id:
+ type: string
+ description: The Stripe invoice ID
+ processor_type:
+ enum:
+ - stripe
+ - revenuecat
+ type: string
+ default: stripe
+ description: The billing processor that owns this invoice.
+ status:
+ type: string
+ description: The status of the invoice
+ total:
+ type: number
+ description: The total amount of the invoice
+ currency:
+ type: string
+ description: The currency code for the invoice
+ created_at:
+ type: number
+ description: Timestamp when the invoice was created
+ hosted_invoice_url:
+ anyOf:
+ - type: string
+ - type: "null"
+ description: URL to the Stripe-hosted invoice page
+ id:
+ type: string
+ description: The Autumn invoice ID.
+ customer_id:
+ type: string
+ description: The customer this invoice belongs to.
+ amount_paid:
+ anyOf:
+ - type: number
+ - type: "null"
+ description: The amount paid in major currency units.
+ refunded_amount:
+ type: number
+ description: The refunded amount in major currency units.
+ required:
+ - plan_ids
+ - stripe_id
+ - status
+ - total
+ - currency
+ - created_at
+ - id
+ - customer_id
+ - amount_paid
+ - refunded_amount
+ description: Inserted or updated invoices in request order.
+ required:
+ - invoices
+ examples:
+ - &a73
+ invoices:
+ - id: inv_2b3c4d5e6f7g8h
+ customer_id: cus_123
+ plan_ids:
+ - pro
+ stripe_id: in_legacy_123
+ processor_type: stripe
+ status: paid
+ total: 29.99
+ amount_paid: 29.99
+ refunded_amount: 0
+ currency: usd
+ created_at: 1451606400000
+ hosted_invoice_url: https://billing.example.com/invoices/legacy-123
+ example: *a73
+ x-speakeasy-name-override: insert
+ parameters:
+ - *a5
+ x-codeSamples:
+ - lang: typescript
+ label: Typescript (SDK)
+ source: >-
+ import { Autumn } from 'autumn-js'
+
+
+ const autumn = new Autumn()
+
+
+ const result = await autumn.invoices.insert({
+ invoices: [
+ {
+ customerId: "cus_123",
+ planIds: [
+ "pro",
+ ],
+ stripeId: "in_legacy_123",
+ status: "paid",
+ total: 29.99,
+ amountPaid: 29.99,
+ currency: "usd",
+ createdAt: 1451606400000,
+ hostedInvoiceUrl: "https://billing.example.com/invoices/legacy-123",
+ },
+ ],
+ });
+ - lang: python
+ label: Python (SDK)
+ source: >-
+ from autumn_sdk import Autumn
+
+
+ autumn = Autumn(secret_key="am_sk_test...")
+
+
+ res = autumn.invoices.insert(
+ invoices=[
+ {
+ "customer_id": "cus_123",
+ "plan_ids": [
+ "pro",
+ ],
+ "stripe_id": "in_legacy_123",
+ "status": "paid",
+ "total": 29.99,
+ "amount_paid": 29.99,
+ "currency": "usd",
+ "created_at": 1451606400000,
+ "hosted_invoice_url": "https://billing.example.com/invoices/legacy-123",
+ },
+ ],
+ )
/v1/invoices.list:
post:
operationId: listInvoices
@@ -30700,14 +31034,14 @@ paths:
before processor tracking count as stripe.
title: ListInvoicesParams
examples:
- - &a72
+ - &a74
start_cursor: ""
limit: 10
customer_id: cus_123
status:
- open
- paid
- example: *a72
+ example: *a74
responses:
"200":
description: OK
@@ -30799,7 +31133,7 @@ paths:
- list
- next_cursor
examples:
- - &a73
+ - &a75
list:
- id: inv_2b3c4d5e6f7g8h
customer_id: cus_123
@@ -30816,7 +31150,7 @@ paths:
created_at: 1759247877000
hosted_invoice_url: https://invoice.stripe.com/i/acct_123/test_456
next_cursor: null
- example: *a73
+ example: *a75
x-speakeasy-name-override: list
parameters:
- *a5
@@ -30895,13 +31229,13 @@ paths:
- entities
title: AttachLicenseParams
examples:
- - &a74
+ - &a76
customer_id: cus_123
plan_id: seat_plan
entities:
- entity_id: user_123
name: Ada Lovelace
- example: *a74
+ example: *a76
responses:
"200":
description: OK
@@ -30980,12 +31314,12 @@ paths:
- entity_ids
title: ReleaseLicenseParams
examples:
- - &a75
+ - &a77
customer_id: cus_123
license_plan_id: seat_plan
entity_ids:
- user_123
- example: *a75
+ example: *a77
responses:
"200":
description: OK
@@ -31056,10 +31390,10 @@ paths:
- customer_id
title: ListLicenseAssignmentsParams
examples:
- - &a76
+ - &a78
customer_id: cus_123
active: true
- example: *a76
+ example: *a78
responses:
"200":
description: OK
@@ -31094,14 +31428,14 @@ paths:
required:
- list
examples:
- - &a77
+ - &a79
list:
- id: lic_asn_123
entity_id: user_123
license_plan_id: seat_plan
started_at: 1759247877000
ended_at: null
- example: *a77
+ example: *a79
x-speakeasy-name-override: listAssignments
x-speakeasy-ignore: true
parameters:
@@ -31127,9 +31461,9 @@ paths:
- customer_id
title: ListLicensesParams
examples:
- - &a78
+ - &a80
customer_id: cus_123
- example: *a78
+ example: *a80
responses:
"200":
description: OK
@@ -31175,7 +31509,7 @@ paths:
required:
- list
examples:
- - &a79
+ - &a81
list:
- license_plan_id: seat_plan
parent_plan_id: pro_plan
@@ -31184,7 +31518,7 @@ paths:
usage: 3
remaining: 7
paid_quantity: 5
- example: *a79
+ example: *a81
x-speakeasy-name-override: list
x-speakeasy-ignore: true
parameters:
@@ -31370,12 +31704,12 @@ paths:
- entity_id
title: CreateEntityParams
examples:
- - &a80
+ - &a82
customer_id: cus_123
entity_id: seat_42
feature_id: seats
name: Seat 42
- example: *a80
+ example: *a82
responses:
"200":
description: OK
@@ -31906,7 +32240,7 @@ paths:
- balances
- flags
examples:
- - &a81
+ - &a83
id: seat_42
name: Seat 42
customer_id: cus_123
@@ -31951,7 +32285,7 @@ paths:
price: null
expires_at: null
invoices: []
- example: *a81
+ example: *a83
x-speakeasy-name-override: create
parameters:
- *a5
@@ -32010,11 +32344,11 @@ paths:
- entity_id
title: GetEntityParams
examples:
- - &a82
+ - &a84
entity_id: seat_42
- customer_id: cus_123
entity_id: seat_42
- example: *a82
+ example: *a84
responses:
"200":
description: OK
@@ -32545,7 +32879,7 @@ paths:
- balances
- flags
examples:
- - &a83
+ - &a85
id: seat_42
name: Seat 42
customer_id: cus_123
@@ -32590,7 +32924,7 @@ paths:
price: null
expires_at: null
invoices: []
- example: *a83
+ example: *a85
x-speakeasy-name-override: get
parameters:
- *a5
@@ -32688,12 +33022,12 @@ paths:
paginated call instead of iterating entities.get.
title: ListEntitiesParams
examples:
- - &a84
+ - &a86
start_cursor: ""
limit: 10
- plans:
- id: pro_plan
- example: *a84
+ example: *a86
responses:
"200":
description: OK
@@ -33245,7 +33579,7 @@ paths:
- list
- next_cursor
examples:
- - &a85
+ - &a87
list:
- id: seat_42
name: Seat 42
@@ -33292,7 +33626,7 @@ paths:
expires_at: null
invoices: []
next_cursor: null
- example: *a85
+ example: *a87
x-speakeasy-name-override: list
parameters:
- *a5
@@ -33481,7 +33815,7 @@ paths:
- entity_id
title: UpdateEntityParams
examples:
- - &a86
+ - &a88
customer_id: cus_123
entity_id: seat_42
billing_controls:
@@ -33490,7 +33824,7 @@ paths:
enabled: true
limit_type: usage_percentage
overage_limit: 120
- example: *a86
+ example: *a88
responses:
"200":
description: OK
@@ -34021,7 +34355,7 @@ paths:
- balances
- flags
examples:
- - &a87
+ - &a89
id: seat_42
name: Seat 42
customer_id: cus_123
@@ -34066,7 +34400,7 @@ paths:
price: null
expires_at: null
invoices: []
- example: *a87
+ example: *a89
x-speakeasy-name-override: update
parameters:
- *a5
@@ -34141,10 +34475,10 @@ paths:
- entity_id
title: DeleteEntityParams
examples:
- - &a88
+ - &a90
customer_id: cus_123
entity_id: seat_42
- example: *a88
+ example: *a90
responses:
"200":
description: OK
@@ -34158,9 +34492,9 @@ paths:
required:
- success
examples:
- - &a89
+ - &a91
success: true
- example: *a89
+ example: *a91
x-speakeasy-name-override: delete
parameters:
- *a5
@@ -34211,10 +34545,10 @@ paths:
- program_id
title: CreateReferralCodeParams
examples:
- - &a90
+ - &a92
customer_id: cus_123
program_id: prog_123
- example: *a90
+ example: *a92
responses:
"200":
description: OK
@@ -34237,11 +34571,11 @@ paths:
- customer_id
- created_at
examples:
- - &a91
+ - &a93
code:
customer_id:
created_at: 123
- example: *a91
+ example: *a93
x-speakeasy-name-override: createCode
parameters:
- *a5
@@ -34292,10 +34626,10 @@ paths:
- customer_id
title: RedeemReferralCodeParams
examples:
- - &a92
+ - &a94
code: REF123
customer_id: cus_456
- example: *a92
+ example: *a94
responses:
"200":
description: OK
@@ -34318,11 +34652,11 @@ paths:
- customer_id
- reward_id
examples:
- - &a93
+ - &a95
id:
customer_id:
reward_id:
- example: *a93
+ example: *a95
x-speakeasy-name-override: redeemCode
parameters:
- *a5
@@ -34363,8 +34697,8 @@ paths:
type: object
title: RewardsListParams
examples:
- - &a94 {}
- example: *a94
+ - &a96 {}
+ example: *a96
responses:
"200":
description: OK
@@ -34578,10 +34912,10 @@ paths:
- coupons
- feature_grants
examples:
- - &a95
+ - &a97
coupons: []
feature_grants: []
- example: *a95
+ example: *a97
x-speakeasy-name-override: list
parameters:
- *a5
@@ -34628,10 +34962,10 @@ paths:
- customer_id
title: RedeemRewardCodeParams
examples:
- - &a96
+ - &a98
code: REWARD10
customer_id: cus_456
- example: *a96
+ example: *a98
responses:
"200":
description: OK
@@ -34662,12 +34996,12 @@ paths:
- reward_id
- entitlements_granted
examples:
- - &a97
+ - &a99
reward_id: reward_789
entitlements_granted:
- feature_id: messages
balance: 100
- example: *a97
+ example: *a99
x-speakeasy-name-override: redeemCode
parameters:
- *a5
@@ -34729,12 +35063,12 @@ paths:
- redirect_url
title: LinkRevenueCatParams
examples:
- - &a98
+ - &a100
organization_slug: acme
env: test
project_name: acme-mobile
redirect_url: https://dashboard.useautumn.com/dev?tab=revenuecat
- example: *a98
+ example: *a100
responses:
"200":
description: OK
@@ -34749,9 +35083,9 @@ paths:
- oauth_url
title: LinkRevenueCatResponse
examples:
- - &a99
+ - &a101
oauth_url: https://api.revenuecat.com/oauth2/authorize?client_id=...&redirect_uri=...&response_type=code&scope=project.read+project.write
- example: *a99
+ example: *a101
x-speakeasy-name-override: linkRevenueCat
parameters:
- *a5
@@ -34820,13 +35154,13 @@ paths:
- env
title: SyncRevenueCatParams
examples:
- - &a100
+ - &a102
organization_slug: acme
env: test
product_ids:
- pro
- premium
- example: *a100
+ example: *a102
responses:
"200":
description: OK
@@ -34892,7 +35226,7 @@ paths:
- results
title: SyncRevenueCatResponse
examples:
- - &a101
+ - &a103
results:
- plan_id: pro
status: synced
@@ -34903,7 +35237,7 @@ paths:
product: created
store_push: skipped
price: set
- example: *a101
+ example: *a103
x-speakeasy-name-override: syncRevenueCat
parameters:
- *a5
@@ -34968,10 +35302,10 @@ paths:
- env
title: GetRevenueCatKeysParams
examples:
- - &a102
+ - &a104
organization_slug: acme
env: test
- example: *a102
+ example: *a104
responses:
"200":
description: OK
@@ -35034,7 +35368,7 @@ paths:
- oauth_access_token
title: GetRevenueCatKeysResponse
examples:
- - &a103
+ - &a105
apps:
- app_id: app1a2b3c4d
app_type: test_store
@@ -35045,7 +35379,7 @@ paths:
environment: production
app_id: app1a2b3c4
oauth_access_token: atk_aBcDeFgHiJkLmNoPqRsTuVwXyZ
- example: *a103
+ example: *a105
x-speakeasy-name-override: getRevenueCatKeys
parameters:
- *a5
@@ -35100,9 +35434,9 @@ paths:
- customer_id
title: MintKeyParams
examples:
- - &a104
+ - &a106
customer_id: cus_123
- example: *a104
+ example: *a106
responses:
"200":
description: OK
@@ -35131,12 +35465,12 @@ paths:
- access_token
- expires_at
examples:
- - &a105
+ - &a107
access_token: am_jwt_eyJhbGciOiJIUzI1NiJ9...
refresh_token: am_jwt_eyJhbGciOiJIUzI1NiJ9...
expires_at: 1781113864000
refresh_expires_at: 1781196664000
- example: *a105
+ example: *a107
x-speakeasy-name-override: mint
parameters:
- *a5
@@ -35179,8 +35513,8 @@ paths:
description: No body. The refresh token is supplied as the Bearer credential;
the response is a freshly rotated access + refresh pair.
examples:
- - &a106 {}
- example: *a106
+ - &a108 {}
+ example: *a108
responses:
"200":
description: OK
@@ -35209,12 +35543,12 @@ paths:
- access_token
- expires_at
examples:
- - &a107
+ - &a109
access_token: am_jwt_eyJhbGciOiJIUzI1NiJ9...
refresh_token: am_jwt_eyJhbGciOiJIUzI1NiJ9...
expires_at: 1781113864000
refresh_expires_at: 1781196664000
- example: *a107
+ example: *a109
x-speakeasy-name-override: refresh
parameters:
- *a5
@@ -35260,9 +35594,9 @@ paths:
- customer_id
title: RevokeKeyParams
examples:
- - &a108
+ - &a110
customer_id: cus_123
- example: *a108
+ example: *a110
responses:
"200":
description: OK
@@ -35276,9 +35610,9 @@ paths:
required:
- revoked
examples:
- - &a109
+ - &a111
revoked: true
- example: *a109
+ example: *a111
x-speakeasy-name-override: revoke
parameters:
- *a5
diff --git a/apps/docs/mintlify/changelog/changelog.mdx b/apps/docs/mintlify/changelog/changelog.mdx
index 9a0113a174..5f7df9e160 100644
--- a/apps/docs/mintlify/changelog/changelog.mdx
+++ b/apps/docs/mintlify/changelog/changelog.mdx
@@ -4,6 +4,53 @@ mode: "center"
description: "Some new things we've shipped at Autumn HQ"
---
+
+ ## License plan customization & propagation
+
+ License plans are now fully customizable per parent. You can override items, prices, and included seat counts on individual license plans, and Autumn rebases those overrides automatically when the base license plan changes — Stripe items and subscription lifecycle stay in sync. [`plans.update`](/api-reference/plans/updatePlan) accepts a `licenses[].customize` block per parent, and responses now include the effective `customize` payload on each license link. Pair with the new `include_license_parents` on [`plans.preview_update`](/api-reference/plans/updatePlan) to see downstream impacts before saving.
+
+ ## Overflow tracking on usage
+
+ [`track`](/api-reference/core/track), [`track_tokens`](/api-reference/core/trackTokens), and [`batchTrack`](/api-reference/core/batchTrack) now accept `overage_behavior: "overflow"`, deducting the full amount past zero and letting balances go negative. Usage-window caps are bypassed, but customer and entity spend limits still clamp — useful when you want to record real usage even when a customer has run out of included balance.
+
+ ## Writable auto top-up purchase counter
+
+ You can now reset or set the runtime purchase counter for [auto top-ups](/documentation/modelling-pricing/auto-top-ups) directly through [`customers.update`](/api-reference/customers/updateCustomer). Pass `billing_controls.auto_topups[].purchase_limit.count` to nudge or reset a customer's purchase count without waiting for the interval to roll over. `count > limit` is rejected with a clean 400, and active windows are preserved.
+
+ ## Automatic recovery for failed customer creation
+
+ Customer create and get-or-create requests that hit rate limits or transient database/Redis errors are now automatically queued and replayed in the background, with globally serialized concurrency and deterministic dedupe. Requests come back with their original semantics and Autumn records whether the replay created or fetched the customer — no more one-off failures during traffic spikes.
+
+ ## License transitions & drop handling
+
+ Plan transitions that drop a license pool (e.g. Team → Pro) now release the affected seat assignments and restore pool capacity in a single atomic step, unblocking migrations that previously errored out. The Attach Product preview warns you when a transition will remove licenses and lists the entities that will be released. Assignments carried across matched successor pools stay intact.
+
+
+
+ - License catalog updates preview and apply cleanly across parent-plan versioning and in-place edits, preserving existing customer license definitions — [#2288](https://github.com/useautumn/autumn/pull/2288)
+ - Row-batched license transitions preserve quantities, billing state, and Stripe linkage across immediate and scheduled changes — [#2306](https://github.com/useautumn/autumn/pull/2306)
+ - Child license plan propagation now previews linked parent-plan impacts and supports explicit propagation targets — [#2295](https://github.com/useautumn/autumn/pull/2295)
+ - Auto top-up thresholds can now be negative, so you can trigger top-ups once a balance dips below zero — [#2354](https://github.com/useautumn/autumn/pull/2354)
+ - New Stripe customers imported via `stripe_id` on customer creation now pull in eligible subscriptions and schedules automatically — [#2308](https://github.com/useautumn/autumn/pull/2308)
+ - Customer dashboard plan filters now resolve in milliseconds instead of tens of seconds on large orgs — [#2362](https://github.com/useautumn/autumn/pull/2362)
+
+
+ - Create-schedule previews now show unavoidable immediate charges (initial subscription lines, automatic tax, invoice credits) so previews match the final invoice — [#2329](https://github.com/useautumn/autumn/pull/2329)
+ - Attach form plan-schedule preview now defaults to **immediate** when unset, matching the UI — [#2345](https://github.com/useautumn/autumn/pull/2345)
+ - Long checkout URLs in the dashboard truncate from the end with a proper ellipsis instead of clipping both sides — [#2344](https://github.com/useautumn/autumn/pull/2344)
+ - License assignments are now treated as active only when their pool link has an active parent, so dropped and matched pool transitions behave consistently — [#2353](https://github.com/useautumn/autumn/pull/2353)
+ - Same-batch customized license plan previews no longer crash on virtual catalog products (empty prices/entitlements) — [#2343](https://github.com/useautumn/autumn/pull/2343)
+ - Catalog sync tolerates partial product collections, missing entitlements, and missing prices without erroring — [#2317](https://github.com/useautumn/autumn/pull/2317), [#2319](https://github.com/useautumn/autumn/pull/2319), [#2322](https://github.com/useautumn/autumn/pull/2322), [#2324](https://github.com/useautumn/autumn/pull/2324)
+ - Versioned license schedules and matched plan versions are preserved through Stripe sync — [#2297](https://github.com/useautumn/autumn/pull/2297), [#2284](https://github.com/useautumn/autumn/pull/2284)
+ - OAuth organization list deduplicates when a user has multiple memberships in the same org — [#2346](https://github.com/useautumn/autumn/pull/2346)
+ - MCP `getCurrentOrganization` schema now matches the `organization/me` API response, adding `id` and tolerating a missing `user` — [#2140](https://github.com/useautumn/autumn/pull/2140)
+ - Rate limits on establish routes now return a standard `429` (no `Retry-After`); check and track routes still fail open — [#2384](https://github.com/useautumn/autumn/pull/2384)
+ - Concurrent migrations no longer duplicate customer plans — [#2282](https://github.com/useautumn/autumn/pull/2282)
+ - Versioned license pricing renders correctly in the plan dashboard — [#2291](https://github.com/useautumn/autumn/pull/2291)
+
+
+
+
## Multi-currency pricing and billing
diff --git a/apps/docs/mintlify/docs.json b/apps/docs/mintlify/docs.json
index 404bfe0588..bd44ff4f95 100644
--- a/apps/docs/mintlify/docs.json
+++ b/apps/docs/mintlify/docs.json
@@ -242,6 +242,7 @@
"api-reference/customers/listCustomers",
"api-reference/customers/updateCustomer",
"api-reference/customers/deleteCustomer",
+ "api-reference/invoices/insertInvoices",
"api-reference/invoices/listInvoices"
]
},
diff --git a/apps/docs/mintlify/documentation/mcp.mdx b/apps/docs/mintlify/documentation/mcp.mdx
index 98dd449649..b9169325e6 100644
--- a/apps/docs/mintlify/documentation/mcp.mdx
+++ b/apps/docs/mintlify/documentation/mcp.mdx
@@ -44,16 +44,21 @@ For a long-lived setup, you can also use an Autumn secret key with Bearer auth:
```
-
+
-[](https://cursor.com/en/install-mcp?name=autumn&config=eyJuYW1lIjoiYXV0dW1uIiwidHlwZSI6Imh0dHAiLCJ1cmwiOiJodHRwczovL21jcC51c2VhdXR1bW4uY29tL21jcCJ9)
+Run in terminal:
-Or add to `~/.cursor/mcp.json`:
+```bash
+claude mcp add --transport http autumn https://mcp.useautumn.com/mcp
+```
+
+Or add to your project's `.mcp.json`:
```json
{
"mcpServers": {
"autumn": {
+ "type": "http",
"url": "https://mcp.useautumn.com/mcp"
}
}
@@ -61,21 +66,32 @@ Or add to `~/.cursor/mcp.json`:
```
-
+
Run in terminal:
```bash
-claude mcp add --transport http autumn https://mcp.useautumn.com/mcp
+codex mcp add autumn --url https://mcp.useautumn.com/mcp
```
-Or add to your project's `.mcp.json`:
+Or add to `~/.codex/config.toml`:
+
+```toml
+[mcp_servers.autumn]
+url = "https://mcp.useautumn.com/mcp"
+```
+
+
+
+
+[](https://cursor.com/en/install-mcp?name=autumn&config=eyJuYW1lIjoiYXV0dW1uIiwidHlwZSI6Imh0dHAiLCJ1cmwiOiJodHRwczovL21jcC51c2VhdXR1bW4uY29tL21jcCJ9)
+
+Or add to `~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"autumn": {
- "type": "http",
"url": "https://mcp.useautumn.com/mcp"
}
}
@@ -103,22 +119,6 @@ Or add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
}
```
-
-
-
-Run in terminal:
-
-```bash
-codex mcp add autumn --url https://mcp.useautumn.com/mcp
-```
-
-Or add to `~/.codex/config.toml`:
-
-```toml
-[mcp_servers.autumn]
-url = "https://mcp.useautumn.com/mcp"
-```
-
diff --git a/apps/leaf/tests/evals/harness/context/createAutumnApiMock.ts b/apps/leaf/tests/evals/harness/context/createAutumnApiMock.ts
index 5c55e0cce4..07590693e5 100644
--- a/apps/leaf/tests/evals/harness/context/createAutumnApiMock.ts
+++ b/apps/leaf/tests/evals/harness/context/createAutumnApiMock.ts
@@ -205,6 +205,7 @@ const defaultHandlers = {
},
getAgentRules: ({ setup }) => setup.agentRules,
getCurrentOrganization: () => ({
+ id: "org_acme_knowledge_systems",
env: "sandbox",
name: "Acme Knowledge Systems",
slug: "acme-knowledge-systems",
diff --git a/firelens.conf b/firelens.conf
index 9035154d76..07d6125be4 100644
--- a/firelens.conf
+++ b/firelens.conf
@@ -1,3 +1,9 @@
+[SERVICE]
+ # Flightcontrol prepends its own SERVICE block. The production AWS Fluent
+ # Bit image accepts an additional block and needs this file registered
+ # before the parser filter can use its built-in `json` parser.
+ Parsers_File /fluent-bit/etc/parsers.conf
+
[FILTER]
Name modify
Match *
@@ -27,9 +33,48 @@
Match *
Add region ${AWS_REGION}
+[FILTER]
+ Name parser
+ Match *-firelens-*
+ Key_Name log
+ Parser json
+ Reserve_Data On
+ Preserve_Key Off
+
+[FILTER]
+ Name rewrite_tag
+ Match *-firelens-*
+ Rule $level ^(TRACE|DEBUG|INFO|WARN|ERROR|FATAL)$ axiom_express false
+ Emitter_Name axiom_express_emitter
+ Emitter_Storage.type memory
+ Emitter_Mem_Buf_Limit 10M
+
+[FILTER]
+ Name modify
+ Match axiom_express
+ Remove time
+ Remove region
+ Remove container_id
+ Remove container_name
+
[OUTPUT]
Name http
- Match *
+ Match axiom_express
+ Host us-east-1.aws.edge.axiom.co
+ Port 443
+ URI /v1/ingest/express
+ Format json
+ TLS On
+ json_date_key _time
+ json_date_format iso8601
+ Header Authorization Bearer ${AXIOM_TOKEN}
+ Header Content-Type application/json
+ retry_limit 5
+ net.dns.mode TCP
+
+[OUTPUT]
+ Name http
+ Match *-firelens-*
Host us-east-1.aws.edge.axiom.co
Port 443
URI /v1/ingest/ecs
@@ -40,4 +85,4 @@
Header Authorization Bearer ${AXIOM_TOKEN}
Header Content-Type application/json
retry_limit 5
- net.dns.mode TCP
\ No newline at end of file
+ net.dns.mode TCP
diff --git a/knip.json b/knip.json
index 0ee79dac32..9b695b5946 100644
--- a/knip.json
+++ b/knip.json
@@ -39,7 +39,8 @@
"ignore": [
"src/external/tinybird/connections/eventsS3.ts",
"src/external/connect/attachSandboxStripeAccount.ts",
- "src/cron/resetCron/runClearExpiredResetCron.ts"
+ "src/cron/resetCron/runClearExpiredResetCron.ts",
+ "src/external/stripe/common/simulateTestWebhookFailure.ts"
]
},
"shared": {
@@ -63,7 +64,7 @@
},
"apps/docs": {
"entry": ["**/*"],
- "project": ["**/*.{ts,js,jsx,css,mdx}"]
+ "project": ["**/*.{ts,js,jsx}"]
},
"apps/sdk-test": {
"entry": ["app/**/*.{ts,tsx}", "lib/**/*.{ts,tsx}", "*.ts"],
@@ -98,8 +99,7 @@
"project": ["src/**/*.ts", "scripts/**/*.ts"]
},
"packages/render": {
- "entry": ["tests/**/*.ts"],
- "project": ["src/**/*.ts", "tests/**/*.ts"]
+ "project": ["src/**/*.ts"]
},
"apps/testbench": {
"entry": ["**/*.ts"],
diff --git a/others/python-sdk/.speakeasy/code-samples.overlay.yaml b/others/python-sdk/.speakeasy/code-samples.overlay.yaml
index b8e46f964d..936b012520 100644
--- a/others/python-sdk/.speakeasy/code-samples.overlay.yaml
+++ b/others/python-sdk/.speakeasy/code-samples.overlay.yaml
@@ -785,6 +785,38 @@ actions:
"plural": "API requests",
})
+ # Handle response
+ print(res)
+ - target: $["paths"]["/v1/invoices.insert"]["post"]
+ update:
+ x-codeSamples:
+ - lang: python
+ label: Python (SDK)
+ source: |-
+ from autumn_sdk import Autumn
+
+
+ with Autumn(
+ x_api_version="2.3.0",
+ secret_key="",
+ ) as autumn:
+
+ res = autumn.invoices.insert(invoices=[
+ {
+ "customer_id": "cus_123",
+ "plan_ids": [
+ "pro",
+ ],
+ "stripe_id": "in_legacy_123",
+ "status": "paid",
+ "total": 29.99,
+ "amount_paid": 29.99,
+ "currency": "usd",
+ "created_at": 1451606400000,
+ "hosted_invoice_url": "https://billing.example.com/invoices/legacy-123",
+ },
+ ])
+
# Handle response
print(res)
- target: $["paths"]["/v1/invoices.list"]["post"]
diff --git a/others/python-sdk/.speakeasy/gen.lock b/others/python-sdk/.speakeasy/gen.lock
index 3de29ad1f6..4b78792357 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: 5f29f84cbded76f22af18749d454e016
+ docChecksum: f81a95ffb32713acb147693f47644f88
docVersion: 2.3.0
speakeasyVersion: 1.762.0
generationVersion: 2.882.0
releaseVersion: 0.4.19
configChecksum: b2caf9ea956e3c3c26e7f639fdd4ff2e
persistentEdits:
- generation_id: e31ec989-5f11-4634-be19-afc9c95533df
- pristine_commit_hash: 97fea45a78b2a214fcd96a4a0d8cd3fb93fa6607
- pristine_tree_hash: a749b813d9717da2ef03340d42f2c64f31debfbe
+ generation_id: 94db51d8-a3ad-4c1e-9b72-d54cf91ac359
+ pristine_commit_hash: 09700e90e0a61d65b07abe2fda558414bde70e99
+ pristine_tree_hash: c91210be739d76d006d83e040ad7f09e225c47bf
features:
python:
additionalDependencies: 1.0.0
@@ -159,8 +159,8 @@ trackedFiles:
pristine_git_object: 4b225a6a18f0583c0a5c0d0f770ef46cd12db337
docs/models/attachautotopup.md:
id: 15110f2cdb28
- last_write_checksum: sha1:c9be4205d04d413b0d6d9c339111c7d792b49208
- pristine_git_object: 17993b67ab2db059bb19591f51f3779ffe8d5546
+ last_write_checksum: sha1:fb7eb4673e19e5cbd55ea66a4dd85d20b766a3c3
+ pristine_git_object: d2ce35bcbdfd111c991acba79d36446b4f39950e
docs/models/attachbaseprice.md:
id: 9d6b5ae8c450
last_write_checksum: sha1:136d86ac185da442a3e452cefaee5098f9fda771
@@ -363,8 +363,8 @@ trackedFiles:
pristine_git_object: 925ea57b3dc4fb417c734ad9eeb3c161d2cfe68a
docs/models/attachpurchaselimit.md:
id: 1b8ea180aebf
- last_write_checksum: sha1:1266d9d7e4acad6420d8656826d1d67c0c6dc3e5
- pristine_git_object: 4976acf64da74f2b82dc3cf776fc9653ff8d1e94
+ last_write_checksum: sha1:2f17f6f2782455e0b21c4bfb4ce318f3644efaa1
+ pristine_git_object: f05d94fd768b7d182934fc7d2edab05b39678652
docs/models/attachpurchaselimitinterval.md:
id: 0a050968da92
last_write_checksum: sha1:829e520883f075fdb853af6f4bd726c891239d95
@@ -663,8 +663,8 @@ trackedFiles:
pristine_git_object: fe865ad29596f3a5c72a5ac55589086ae8b20064
docs/models/billingupdateautotopup.md:
id: 5b3d651d9246
- last_write_checksum: sha1:6ffec6bca0b127ab2bce7af83dbc7f567a8750c6
- pristine_git_object: 13c752e910ef8397fbad049040098047ff76f8c9
+ last_write_checksum: sha1:fa9f8653d5c58ce0e66ba3a44c2c24d662c03c5e
+ pristine_git_object: 8d434095c4b1dd27dee529a676544df57591f831
docs/models/billingupdatebaseprice.md:
id: c632c15479cf
last_write_checksum: sha1:e76ef3c07fb0a53af8c4ae9981b07e379191fbd2
@@ -839,8 +839,8 @@ trackedFiles:
pristine_git_object: 2fe315798c7425eea4b333ea5a3be11455a5e45d
docs/models/billingupdatepurchaselimit.md:
id: e8ab03617105
- last_write_checksum: sha1:914aa71b58dfd8ce1df2d8b6eb5ee9832ad1b862
- pristine_git_object: a1be7a56fba22870807e81d2a7a3f6e77cf58dcb
+ last_write_checksum: sha1:a53558e42bd7a34922b052fef8b4082037780b00
+ pristine_git_object: 14a94398d03db8e932e2ecebd316fc61e2a93bc7
docs/models/billingupdatepurchaselimitinterval.md:
id: ac34c00d2a69
last_write_checksum: sha1:b95040a12884533e450cc65f29b7708e769fe597
@@ -1507,8 +1507,8 @@ trackedFiles:
pristine_git_object: 18912b7d8ea75eb28d8c7b26b260cbe0b7b3e5ac
docs/models/createplanautotopuprequest.md:
id: 5b8c1f2c1e5e
- last_write_checksum: sha1:a42a98ee8e83e237d26c7acdc9aafdc541ef517c
- pristine_git_object: 7f2952912916622cc4c2c10254b69609fadf8e25
+ last_write_checksum: sha1:3d5eec21a14c5a8c1953a626a1f6cdf22aa1cfb7
+ pristine_git_object: ddcc38d3427e9c4386502784fb7eb934f9be1aef
docs/models/createplanautotopupresponse.md:
id: 95f17e3ab250
last_write_checksum: sha1:bb0b532ed8193b821de82784146e057cf4c11088
@@ -1715,8 +1715,8 @@ trackedFiles:
pristine_git_object: 447af59f78baa29a13e33aeb45f58d7ab338bbcc
docs/models/createplanlicense.md:
id: bb1159dc173b
- last_write_checksum: sha1:07ba830c9fd6ecbe366e29b20b9c06965d426e76
- pristine_git_object: af69a297101e5e81a1bd20fa546b45c8cd9fc630
+ last_write_checksum: sha1:ce7c37a8fdb4d391a8308e3c552e1baa935a1232
+ pristine_git_object: a03eb3be825b560365401cfa9cda38aef5865b90
docs/models/createplanlicenseadditemadditionalcurrency.md:
id: fe0de8969329
last_write_checksum: sha1:d5b242f4d1be2ae9d0c2ca45b422dd7e842615d2
@@ -1891,8 +1891,8 @@ trackedFiles:
pristine_git_object: d0186ff49ec916df60ab6b1b6a767fd0894cde84
docs/models/createplanpurchaselimitrequest.md:
id: 071c769e81ec
- last_write_checksum: sha1:dda3f921fbef9455bd18b263adf4eeda75338eb9
- pristine_git_object: 8d777218a7e21f7ac54c4ad9d9d3cb0ce9300cf7
+ last_write_checksum: sha1:eab27cc21dd4a2f02c0648620ff4bbf7cc7c3d88
+ pristine_git_object: da8bf517a90de32afe03540dcea9b65ca00caa9d
docs/models/createplanpurchaselimitresponse.md:
id: 6c1246186aa6
last_write_checksum: sha1:7b869cd5b84e9c84e31b6cbefe81eb35c2adb51c
@@ -1975,8 +1975,8 @@ trackedFiles:
pristine_git_object: 6a253774e18e4e86bae8ce4a58269d0db49df4e5
docs/models/createplanvariantdetailsautotopup.md:
id: c984520c75ca
- last_write_checksum: sha1:05612b5707e4599528357b6823bec8bfc71b70c1
- pristine_git_object: bc5281cf86028f7dc0f00cc6adb5b399d39c53c2
+ last_write_checksum: sha1:6f76843c667c739300a7990807de3f33a2bd0779
+ pristine_git_object: 698ce80b6cf5b8677695a773ffa5a02be9a5540e
docs/models/createplanvariantdetailsbillingcontrols.md:
id: ae5beb6e1689
last_write_checksum: sha1:57ee7a8210dbc93cdd5a9ef98a7ea7b2d0953c68
@@ -2023,8 +2023,8 @@ trackedFiles:
pristine_git_object: 917de0238e95eaefae558f949050655ab15cd876
docs/models/createplanvariantdetailspurchaselimit.md:
id: c7aef1c5ade3
- last_write_checksum: sha1:4fb7d33541cf232bbb13d9c4b2f36b3100c69df0
- pristine_git_object: ea15306d0dbbf0c074d9669add6a1df6e449cd63
+ last_write_checksum: sha1:ac4e46903f3de9792d294648171d822b763175fd
+ pristine_git_object: f276f58047d94fae2d8910eff6fed2e9ad113d58
docs/models/createplanvariantdetailspurchaselimitinterval.md:
id: 087c419f9a82
last_write_checksum: sha1:eee7a5787e37fe90c7867ca7aafa5e771ce7ac8e
@@ -2159,8 +2159,8 @@ trackedFiles:
pristine_git_object: 115eb2fd82f0358d49f960ddca8e8f651fc8b33f
docs/models/createscheduleautotopup2.md:
id: 06f15100a5d2
- last_write_checksum: sha1:a9ff84d7e6ae6cd7789dee8c3f02e52085f1eec8
- pristine_git_object: 6dde81cc563717b7e615c442528defbb29e42c72
+ last_write_checksum: sha1:22d0b7ee3f4501b061f4b1138dcfa0b3b3226cf3
+ pristine_git_object: b8d533146470d265d8e521c62db91bc94f6a55ea
docs/models/createschedulebaseprice2.md:
id: 085a5cb811d8
last_write_checksum: sha1:def7fa9dda15e1bc7f0275f1cff1a83a898da0a6
@@ -2299,8 +2299,8 @@ trackedFiles:
pristine_git_object: 1e05140406e5b892611a0e99e19efe47887bdf90
docs/models/createschedulepurchaselimit2.md:
id: ba44df61a018
- last_write_checksum: sha1:c7932a218fd8c1142f9eddd3ed60531209639ea8
- pristine_git_object: 2684491f0d5971a475cd1fa511a4c32ecd40485a
+ last_write_checksum: sha1:c79e2274b3f06f779873e11f4396ffec2ec27d20
+ pristine_git_object: cce0a613efdf929fb21353d00b53858f2d88e91d
docs/models/createschedulepurchaselimitinterval2.md:
id: 710eceb9eac1
last_write_checksum: sha1:6462a9c6fed4b22781eae8a1433fded5378ca1b5
@@ -2371,8 +2371,8 @@ trackedFiles:
pristine_git_object: 322f87ce6357ae1918cd174de701fca6fc93bd77
docs/models/customerdataautotopup.md:
id: d37b23cdcc27
- last_write_checksum: sha1:599e96167eead0ea87a6b3965819019ea4437f01
- pristine_git_object: cf6e86760fcb5e8d9a375b62760578fc71f70d20
+ last_write_checksum: sha1:433e9c8b113eb943d47903cdded0b1954e598c8d
+ pristine_git_object: a5d99fd5517ed078f9dbfd8d2a61c0b52e866a53
docs/models/customerdatabillingcontrols.md:
id: 9a9f4fd47b8a
last_write_checksum: sha1:5a4fbe5d38c490cca6cd4c41fd78725cacac86c6
@@ -2395,8 +2395,8 @@ trackedFiles:
pristine_git_object: 4e59bfa9db3b3f1293ce0cda920140ea981b9e09
docs/models/customerdatapurchaselimit.md:
id: c415017a8c4d
- last_write_checksum: sha1:5fbe7355e01e4dabb7743d839402502e3d60276d
- pristine_git_object: f08f426d51c40b4642f63f55e8005de29868f9c9
+ last_write_checksum: sha1:754e8cccbf67011908c56579d574d104ea7e9f42
+ pristine_git_object: 4c66c61268b24daa0f61591d7bf7d1b07a3af5b0
docs/models/customerdatapurchaselimitinterval.md:
id: 2f2f6406b75e
last_write_checksum: sha1:08a5e556685e524455fe6628ca03903bf26c4b50
@@ -3095,8 +3095,8 @@ trackedFiles:
pristine_git_object: f5f28a920ccbcc244822b0994dcf89419eef72f7
docs/models/getorcreatecustomerautotopup.md:
id: 2beff692c107
- last_write_checksum: sha1:980d9d480f667e5f75ee005128a4023488a139bd
- pristine_git_object: 71910553ee8796a2dfbe5c63d62515ab63b0c395
+ last_write_checksum: sha1:b34a5b6887423474a02f17f6464386491cff0896
+ pristine_git_object: 6574c394f189b419b8f4be217084634cfdc8e05c
docs/models/getorcreatecustomerbillingcontrols.md:
id: c863934c5751
last_write_checksum: sha1:2e5c116229b1061befedcba9938cb00ebf3b4d0c
@@ -3131,8 +3131,8 @@ trackedFiles:
pristine_git_object: 63394b3bc1cc7c1670c5212a6b3d813c0b3b8ef1
docs/models/getorcreatecustomerpurchaselimit.md:
id: "425587326514"
- last_write_checksum: sha1:d1d028dde2dcade9702ce1a70966f045baf49c74
- pristine_git_object: 324803fdec501d444e3e929e13aa29cbff75b39d
+ last_write_checksum: sha1:337d4a049f4b9c0eed1fce47d7485ffdf571cfc5
+ pristine_git_object: 6750d5d3b725807dfad3ae61a657190173af537d
docs/models/getorcreatecustomerpurchaselimitinterval.md:
id: b8251af4ba20
last_write_checksum: sha1:82af2be0dc953f3f71c37eea8be1f823f195cd76
@@ -3411,8 +3411,8 @@ trackedFiles:
pristine_git_object: 91a6df1aba96e704ab4757be8aefafc3e228cd98
docs/models/getplanvariantdetailsautotopup.md:
id: 00dbb93a61e3
- last_write_checksum: sha1:4ba39bc774344474c74537948402b7de6539a68b
- pristine_git_object: 5ae0bb51165473bfeaa0f5cf2b5e899118129846
+ last_write_checksum: sha1:5fbc4ed00eb8049c9c6181517bde4921e8786014
+ pristine_git_object: fb229fe4e21894aaeca708018ee3cd0bda336e49
docs/models/getplanvariantdetailsbillingcontrols.md:
id: 1547325cf915
last_write_checksum: sha1:e5ecdcceead9de4e368c8f3afe4838b6bf365f0c
@@ -3447,8 +3447,8 @@ trackedFiles:
pristine_git_object: da6f6059e2dcd671ee16e5082bb6de10286880e8
docs/models/getplanvariantdetailspurchaselimit.md:
id: 755524a499a6
- last_write_checksum: sha1:a31a17a34b17d42e7d0646f1756389c6f48cbac0
- pristine_git_object: 772a730e464769cdb6238dbb68f414b818c541b4
+ last_write_checksum: sha1:efd9486cc077f47bc8562bb6396636d21601d8ea
+ pristine_git_object: 6c3293e65c6ae1d11fc1ce69308f16fd968f7745
docs/models/getplanvariantdetailspurchaselimitinterval.md:
id: 8b09af747476
last_write_checksum: sha1:f0b33809106d249845b2b0957629bc18299808da
@@ -3577,6 +3577,34 @@ trackedFiles:
id: a866162d4224
last_write_checksum: sha1:6e8b4bdfa39d20c43de5bfe3f4d40c2e4e63405f
pristine_git_object: d113b4a74c800629028857f59b3367aefb73dfee
+ docs/models/insertinvoicesglobals.md:
+ id: 896556440fa4
+ last_write_checksum: sha1:a45db96ededd519bf05008ace80057af442dcf76
+ pristine_git_object: dce2abacca661b46a44cba5d5fbfecf665509fdb
+ docs/models/insertinvoicesinvoiceresponse.md:
+ id: 156dca2629c3
+ last_write_checksum: sha1:80666717648be6832a10a433afef87d2fb6700fb
+ pristine_git_object: 707e172ee422e33df0f31c90ce515b0ff27d61ef
+ docs/models/insertinvoicesparams.md:
+ id: 893d7e76c910
+ last_write_checksum: sha1:0f0c18790c9947972015db134844b9ee5000ce43
+ pristine_git_object: 8acd64990dc16cecfa19a69356740c1b104858a2
+ docs/models/insertinvoicesprocessortyperequest.md:
+ id: 55f3b6a2fff0
+ last_write_checksum: sha1:af22d90204a44b4f15d93a72acbfe962439dbb5f
+ pristine_git_object: fb73649ba669cd179b6dfb637e6c5ba388c90ca8
+ docs/models/insertinvoicesprocessortyperesponse.md:
+ id: 99db41b8f870
+ last_write_checksum: sha1:51062e66a9f00fa37c7fdb6116860a88c86eb485
+ pristine_git_object: a89798aad594fafd47bc390d7c65bc8c0570eaa7
+ docs/models/insertinvoicesresponse.md:
+ id: 43fe0576c3fe
+ last_write_checksum: sha1:2de57a074f8d997296bf7d421e011ae44d8f4451
+ pristine_git_object: 78a1bce92cb3204819bbe76fb2539217b20a266b
+ docs/models/insertinvoicesstatus.md:
+ id: 6a6fea5e7446
+ last_write_checksum: sha1:ef37186e1ae62dbc283fd401c356768977aa7422
+ pristine_git_object: b83f5d07a153815d5148db9f6665fee2dc492e9d
docs/models/intent.md:
id: a4a6fa8818f7
last_write_checksum: sha1:9079fedc03e6b5c3469c752774a972f27bdab857
@@ -3597,6 +3625,10 @@ trackedFiles:
id: 18e2034f11ad
last_write_checksum: sha1:be8a764edc2849da9e554de10fa04005cbe9eed9
pristine_git_object: b58012425affb550214b7d1799072123933f2f3c
+ docs/models/invoicerequestbody.md:
+ id: 44fc14c7ffe3
+ last_write_checksum: sha1:973d9ad0919ebc4bb4fb2195d7830df61291ca40
+ pristine_git_object: 018dfbb87b09c163c4b4c0ebc8276e45740d31ab
docs/models/item.md:
id: 40dd7473ab87
last_write_checksum: sha1:4e2e7c45d325aa5eedab794f0d5ffce51c2d7e0d
@@ -4043,8 +4075,12 @@ trackedFiles:
pristine_git_object: 301869758bccff1b9ef10091d15fdf4569adf99c
docs/models/listinvoicesparams.md:
id: 9ec138cd60df
- last_write_checksum: sha1:750a629ea2bc230dfd96abe000aab2aefb8a9b9d
- pristine_git_object: ce7dffb6635949f3fce490e4b64a2c68d7f44e44
+ last_write_checksum: sha1:6ca44b0c5e6fe1a65cba3cb9e81dda1174f0bd08
+ pristine_git_object: a9a3c5b90e2aac581bc9fec44cce0f82c9687a17
+ docs/models/listinvoicesprocessortyperequest.md:
+ id: 20842fa2267e
+ last_write_checksum: sha1:95e2bc40f4a6c716cf23146a35f806ca7f2409a4
+ pristine_git_object: 5b5ba72368cc43864eda156016c20eb83aaa0f43
docs/models/listinvoicesresponse.md:
id: 9eafac82c643
last_write_checksum: sha1:245400ba75d9506554386d1ea7c55a6407f6951f
@@ -4311,8 +4347,8 @@ trackedFiles:
pristine_git_object: b08aad360f30f4657b9ccb89ec95b7aa745acfba
docs/models/listplansvariantdetailsautotopup.md:
id: 20c33dc6e5ca
- last_write_checksum: sha1:be638999b8c60289754d96b0e0a6f1bc0349bcdf
- pristine_git_object: 94983a79195b5126e42d75377423cdf762ce66a0
+ last_write_checksum: sha1:bcf4b841e6399c02d3082460342f32aeb324448e
+ pristine_git_object: 339362f7c26ece090297b0b458a6d458ad189c43
docs/models/listplansvariantdetailsbillingcontrols.md:
id: f5477e249645
last_write_checksum: sha1:fc56f310f7de1696d8780147cbf0e0c67306e46c
@@ -4347,8 +4383,8 @@ trackedFiles:
pristine_git_object: 2ddce0ac83d7fea8823fe7e619c0b6b81544830a
docs/models/listplansvariantdetailspurchaselimit.md:
id: b631a937a083
- last_write_checksum: sha1:8e5c403a8dafae0d2987c78783cb452234d91d25
- pristine_git_object: d75739f14a5ec9173d1b76e3bc502093ccd321a0
+ last_write_checksum: sha1:e759fb7f6d414cd3e81cc1fe16fd3f31de75cbf0
+ pristine_git_object: 717cf18603cdbe6797541bf5696cc9092cf140c8
docs/models/listplansvariantdetailspurchaselimitinterval.md:
id: 55bcccf59241
last_write_checksum: sha1:c2a4993f94ca705901d705eb9987aa490eeec120
@@ -4855,8 +4891,8 @@ trackedFiles:
pristine_git_object: aef5b6c1bc30b6efa4eca01abcdc24c6e5cbd2dc
docs/models/planvariantdetailsautotopup.md:
id: 4ec4f15fa7c1
- last_write_checksum: sha1:73355bfb2509464f6cc00e1534c15fed48585086
- pristine_git_object: d23d7b5a6e28230d24a9884646cc040ac0c67c99
+ last_write_checksum: sha1:b9e4b49e925a3ed3e19545298fe4c6c0b2a6dfc8
+ pristine_git_object: a40cd869b27123408574fcfdb4a3661ec4f26b13
docs/models/planvariantdetailsbillingcontrols.md:
id: bded401054c6
last_write_checksum: sha1:3fae1efce480f700b96060becaa2a93396c07a2d
@@ -4883,8 +4919,8 @@ trackedFiles:
pristine_git_object: ced9f5af8e6be7355149cdf978cfab022958e1e9
docs/models/planvariantdetailspurchaselimit.md:
id: a9bb69a6fe6a
- last_write_checksum: sha1:b75ba6a6dde3d65d901ae816977f69ff6721d25f
- pristine_git_object: 91ee2745ab4bf483facb6c29644f9abe67b26e79
+ last_write_checksum: sha1:757ca9f7e750e346c024e2a8a88aaf905bddaf17
+ pristine_git_object: 9f8c602b2da21ce474f72acca12f90ed3436b0cf
docs/models/planvariantdetailspurchaselimitinterval.md:
id: 933acdf1458a
last_write_checksum: sha1:fa74328ea572dde6724182eea1688f640f878b19
@@ -5015,8 +5051,8 @@ trackedFiles:
pristine_git_object: 9d29579d60ad03509a0c3a9322beea8adc82f55a
docs/models/previewattachautotopup.md:
id: 65bde9ecb54d
- last_write_checksum: sha1:2627dba7fd4fd1f3524ffaf9eb7f6987f7c72979
- pristine_git_object: 9b49268d7642e604708a44d66adac9acf1e63720
+ last_write_checksum: sha1:daa89e8e7d748894416d06955635d6530cc9d3aa
+ pristine_git_object: 3d6896260bdb77f524c28642456435ff0b9ba957
docs/models/previewattachbaseprice.md:
id: 7c2843459273
last_write_checksum: sha1:97d8c02b3aebcc3ec12ec4f7b8d0bff60042f056
@@ -5247,8 +5283,8 @@ trackedFiles:
pristine_git_object: 1cc089658a20a07a4f3d3a449f7373cf36037a74
docs/models/previewattachpurchaselimit.md:
id: eaf96494549f
- last_write_checksum: sha1:a5c47167f681c88878b8ee1f1b0d1fd7fc987a6d
- pristine_git_object: 7adfbaead71c98d79abd0e74f96b50c312f0faf3
+ last_write_checksum: sha1:0fe5b8c585d412b5685b06bdb5d7345c65c4b2f0
+ pristine_git_object: d1e494fc2aa9e80d35c8203901f6b9c1636f05d1
docs/models/previewattachpurchaselimitinterval.md:
id: c151362a7be0
last_write_checksum: sha1:74cfeae33a66c2ca173f550e9d745090fd8a88be
@@ -5771,8 +5807,8 @@ trackedFiles:
pristine_git_object: db64b2ae127d3a36dd6ae6b7f58c3b22cfa3f1b6
docs/models/previewupdateautotopup.md:
id: cf7e5007afc5
- last_write_checksum: sha1:452112047f7e01f0e2d0d89aec19f7e982199b81
- pristine_git_object: 5c4ec762f6994de1fe6138c449c5cf8df9d89f64
+ last_write_checksum: sha1:0ec22052037019df1dee8ece7d9a9db636366201
+ pristine_git_object: f707b6720ed744efad086cf45149fa73d86a9bbd
docs/models/previewupdatebaseprice.md:
id: 7eea7a31d277
last_write_checksum: sha1:3dabf1a24414b7e207019e974f6097c5bf45210f
@@ -5991,8 +6027,8 @@ trackedFiles:
pristine_git_object: f09eb4baade271cd90673805ea85230e5fbc9c49
docs/models/previewupdatepurchaselimit.md:
id: 66a59b49f6c1
- last_write_checksum: sha1:e34de5a2ef1bad2bcaf5e444da3ed4c2368e4921
- pristine_git_object: ac502e3dab64b3e1496fcbe72178036490872393
+ last_write_checksum: sha1:4e4d1df2b51e23aba0dc3a1de6e0ca777e7db2d2
+ pristine_git_object: 1d794c8aad8191cb4f566f0daf194e38333dc015
docs/models/previewupdatepurchaselimitinterval.md:
id: 13adbd8dcb91
last_write_checksum: sha1:0ba67c0c7c983475b49d49ebbecf8bc8364e11c8
@@ -6149,10 +6185,6 @@ trackedFiles:
id: 446cf8386114
last_write_checksum: sha1:c917a956f1af010a60b2e7d43bad09cf54e44274
pristine_git_object: c42de1ea48789d1cd09dcce226e3673f29b2a747
- docs/models/processortyperequest.md:
- id: 2a044477e341
- last_write_checksum: sha1:9caa03e4659e2c364b2cd7d65425d1a79a52d22c
- pristine_git_object: 57f091070525d10d5f0ac09b3363a02ae749f579
docs/models/productdisplay1.md:
id: b5bdefcde7af
last_write_checksum: sha1:4cbda567685f39d23b4a83d5a9bc20c4bf2fa21b
@@ -6371,8 +6403,8 @@ trackedFiles:
pristine_git_object: 53fc7933fee507da4b362252d0a4cfc577d17536
docs/models/setuppaymentautotopup.md:
id: f8823dfe9bb8
- last_write_checksum: sha1:c1a7583624a45319cfba427d7b9c3044b3a81e6b
- pristine_git_object: c97fc39d68c0a45d49dd6f338b967b4f2f0dcd67
+ last_write_checksum: sha1:89f949b4687280c1bf2f67b8ae1484db4f763021
+ pristine_git_object: 7f5424f6e9260c805d907ad5609e70027b4b0e5a
docs/models/setuppaymentbaseprice.md:
id: a123b4da0cd9
last_write_checksum: sha1:8e848145fe4e5658f781d2b7bf8f335f45df7949
@@ -6543,8 +6575,8 @@ trackedFiles:
pristine_git_object: b79cc25122d2f3e75b5504ac2ab52c1f1ff527cf
docs/models/setuppaymentpurchaselimit.md:
id: c48cf8871c7d
- last_write_checksum: sha1:e79cc44eb67e609bbb1dfa2c2dabdf0226d82b4c
- pristine_git_object: 088f8a4fab9aa814b52db52fd746052eae83267f
+ last_write_checksum: sha1:d797346c0c9e37932287987039d8268420e187ae
+ pristine_git_object: 6227468d8c0a52ee2c7b36d649a5a19cb4951c62
docs/models/setuppaymentpurchaselimitinterval.md:
id: 7865886ab45a
last_write_checksum: sha1:16186c61933c15f0b1de65c62a101c21cf556563
@@ -6863,8 +6895,8 @@ trackedFiles:
pristine_git_object: e2df03126ca9aa71027bcd8f9e8def06b4a6e612
docs/models/updatecustomerautotopuprequest.md:
id: 39142e7d45b0
- last_write_checksum: sha1:604a27462c5217342eba9f56ee3f926148841a66
- pristine_git_object: d576ca1d37b88d129169b9331e2ad509b395ed3f
+ last_write_checksum: sha1:5e4598d420a7dc0433b9c0418ae13ed7047819c6
+ pristine_git_object: b68764fa0895acb91cd9c833c2ab93b28064068b
docs/models/updatecustomerautotopupresponse.md:
id: c57d6aa94fbc
last_write_checksum: sha1:4f153c173360fc6fcc8cbdb03e09e1da68924a09
@@ -6983,8 +7015,8 @@ trackedFiles:
pristine_git_object: 8ecdcfc2e320fe3c2bfd6e923b01ed5a1788417c
docs/models/updatecustomerpurchaselimitrequest.md:
id: e0b5bae8abfe
- last_write_checksum: sha1:10242d53f27a2a7d4a79da4896f4613149cee33d
- pristine_git_object: 3be65ea39fee47ad7839606ec40c91548819e4b5
+ last_write_checksum: sha1:922d69ff13d7fb7858d48df2f078c608958e612d
+ pristine_git_object: 6d530408dce896b2b15eb814ae175b20da8317d6
docs/models/updatecustomerpurchaselimitresponse1.md:
id: accb82caac88
last_write_checksum: sha1:3c2bf9fd114c8d1b206813c20568f607b8b57791
@@ -7301,6 +7333,10 @@ trackedFiles:
id: 0ba0a862dcca
last_write_checksum: sha1:ba4000eac9f25f6713b827f89da999285b58cc65
pristine_git_object: 18e672cee28b83060d88007bb59a207cdc9629e0
+ docs/models/updatelicenseparent.md:
+ id: fb6397bab933
+ last_write_checksum: sha1:88ce39f7b5fc556228d474287d78ee953da1e1fe
+ pristine_git_object: 3140fad320aedbe89065b1e8ae6bd07ab41a8679
docs/models/updateplanadditionalcurrencyrequestbody.md:
id: 26d669eae669
last_write_checksum: sha1:7e0bdebafca3486b1a1876d9a99ca570fbe96b20
@@ -7315,8 +7351,8 @@ trackedFiles:
pristine_git_object: ad36f93503838af401489616dfce1916dccb08f4
docs/models/updateplanautotopuprequest.md:
id: 728d617ae5eb
- last_write_checksum: sha1:f5cb8ee53cebc6b16401faa7635b7d63ecd80234
- pristine_git_object: a5e748d059d65a84c79d16736cb2a87e8550e72d
+ last_write_checksum: sha1:334ded4ea3a144b5d6bce2aa1d97e020ca42d5d8
+ pristine_git_object: ef5c3b4edb5abce2b0e3678feb7c3f725502fc76
docs/models/updateplanautotopupresponse.md:
id: 4fe573b999aa
last_write_checksum: sha1:3aaeed8eed9cb2b1571f7eda2af99a1c8cb0f4ca
@@ -7531,8 +7567,8 @@ trackedFiles:
pristine_git_object: 432c4fb72f3384999ee204702b893a5487ec9a74
docs/models/updateplanlicense.md:
id: 6a25f2678c05
- last_write_checksum: sha1:1035b10b12d5f8eec4dad29ecef762227d278860
- pristine_git_object: fe62f77263f8cadb0c1c921cf4fcbb943f730b30
+ last_write_checksum: sha1:255422f5f98945a05d446b36b252bfffcdbb4255
+ pristine_git_object: 759ac8e540eea5123caf2aca065121f180756f9f
docs/models/updateplanlicenseadditemadditionalcurrency.md:
id: 57a5ce673d19
last_write_checksum: sha1:2f9c3819331ab4c4bb4509c0c7487cb66736af30
@@ -7647,8 +7683,8 @@ trackedFiles:
pristine_git_object: b849611da8ce48f7a76c6a8822e84a9d7e9b0db4
docs/models/updateplanparams.md:
id: ca9b432d1066
- last_write_checksum: sha1:bf230f0833d1034438be47c70f80c1afaf5a6c12
- pristine_git_object: 11908e7a23fe21a46f9ed14c2752e2f75a01306f
+ last_write_checksum: sha1:d20ee9530005cc04254f35d80a96d84aa4d0356d
+ pristine_git_object: b2b182541d46f04595f41e1b888f9eb7b3a86015
docs/models/updateplanplanitemfilterresponse.md:
id: 092ca30ea3f6
last_write_checksum: sha1:3fc7dff4908aff333620e77c741255c23219731e
@@ -7703,8 +7739,8 @@ trackedFiles:
pristine_git_object: cc9b07ca8c2ac1efd152d8ba6766a21940b73875
docs/models/updateplanpurchaselimitrequest.md:
id: 058893a720b3
- last_write_checksum: sha1:c128844e4e0059c0bf75632cdb2d05226f5237c7
- pristine_git_object: bec49bd4b4b021c30caefa5fd8095b5d7158c562
+ last_write_checksum: sha1:951867d0765f3cf713909bfe41652719fa6eb806
+ pristine_git_object: 54e83e8c18dbab2a886f3c5f039b0849027a7b6c
docs/models/updateplanpurchaselimitresponse.md:
id: 025600dee2ce
last_write_checksum: sha1:be4ceea4313f227268f0000a24fb22b25dedb447
@@ -7787,8 +7823,8 @@ trackedFiles:
pristine_git_object: b39d03627b6cbe2c35595f1b681f23fd30c1ee71
docs/models/updateplanvariantdetailsautotopup.md:
id: f66104927470
- last_write_checksum: sha1:89c00bf1d4e4685bff349435fd2a109ad54380c8
- pristine_git_object: 8f895b641858560dde6e168cd092548df6381040
+ last_write_checksum: sha1:b357ad2d82564b4d0af588c39fd7d8a3ef239123
+ pristine_git_object: e084c5697d128d10dbbdcaeea7d4ae00853100bd
docs/models/updateplanvariantdetailsbillingcontrols.md:
id: 99b1c1fbf647
last_write_checksum: sha1:f6ce128f0ff39d91b4e4473289e240b64e1aa241
@@ -7835,8 +7871,8 @@ trackedFiles:
pristine_git_object: eb32c3ad5079eb3852d69a1954f153e1ee745f3a
docs/models/updateplanvariantdetailspurchaselimit.md:
id: 22b1b43959b1
- last_write_checksum: sha1:8d21a9d5159482abcf488b7c9f4cde1a142c9edf
- pristine_git_object: 5188545d44c37b4ca6870578057fc3e15c82774b
+ last_write_checksum: sha1:dee286b8214d0208e152cb3696ddbcd555b53de6
+ pristine_git_object: 45cd6001907ceef6d84b236d59733bae77612a22
docs/models/updateplanvariantdetailspurchaselimitinterval.md:
id: 98af6395686b
last_write_checksum: sha1:d13a16bcddca61b0849e29ec81d53d1cfbb6d7cc
@@ -7939,8 +7975,8 @@ trackedFiles:
pristine_git_object: c885af85d97cba500333c381cb34b08840d5d7c6
docs/models/variantautotopup.md:
id: 88d5f637822a
- last_write_checksum: sha1:eb0debe413221244d34df3d3a87a65b50123b480
- pristine_git_object: d0ef4aeb7abae13d434b35ac7feec3fc00d44a19
+ last_write_checksum: sha1:dfe6aafebb85059dac5f3498374bb60c91996c7d
+ pristine_git_object: 0902bb8314c81bf9bed43ddce63f2e7c21139796
docs/models/variantbaseprice.md:
id: f24c0ca3e17f
last_write_checksum: sha1:15243e2252c69a2c0fca2b6c156e831586de495f
@@ -8039,8 +8075,8 @@ trackedFiles:
pristine_git_object: a1a72d6862a3facb180ccdfa7deab63822f0845d
docs/models/variantpurchaselimit.md:
id: 44a108b4dc23
- last_write_checksum: sha1:2ad6c8d5115a9c8ed9524e895dff6f7c4fe0bd12
- pristine_git_object: edd0f8c25af520bfcf5ac4759d1561961892fef7
+ last_write_checksum: sha1:c8eda2c641b5ef7d6e1647a6a8ecf7f59aeddb00
+ pristine_git_object: 1d9cfc8683fcee03fb56163196813e6c1b9da44f
docs/models/variantpurchaselimitinterval.md:
id: 7092bab14de4
last_write_checksum: sha1:58ebc09fdb19add7c99836b73b687e686d07cd9c
@@ -8131,8 +8167,8 @@ trackedFiles:
pristine_git_object: 337f58429b8e20186d1780093f18e4bf96c6133e
docs/sdks/invoices/README.md:
id: a3f76a06eded
- last_write_checksum: sha1:0aa1b7c06e2fe4395fe1197735e44a7972d3438e
- pristine_git_object: a37d17dd6cbbfa20fdaef4b596c92afde0600b51
+ last_write_checksum: sha1:6bc7ac5b0095cca09a281822ec02cd9065dfef39
+ pristine_git_object: 3bce2dae27e55339ee5923f0415e419e8eef5683
docs/sdks/keys/README.md:
id: 9a567cd0daf2
last_write_checksum: sha1:feda26eeb2d8049e97f4f40bde5479981a7b5e39
@@ -8143,8 +8179,8 @@ trackedFiles:
pristine_git_object: 91a73d64b8b66030bf05508df0353b634584b6bc
docs/sdks/plans/README.md:
id: 2d8c741fff57
- last_write_checksum: sha1:f6e172676e159ba2c3c00f6d68333ee45ad975d2
- pristine_git_object: 224bd77e9ca1e30436ec68d61fd3b6b4cc2333a2
+ last_write_checksum: sha1:ef0674fc6cc00f06162dcce0880968f0c82a8742
+ pristine_git_object: 06b99615d5cb056e7ae0d9f1a2ca4e768206d581
docs/sdks/platform/README.md:
id: b66219e9cd4d
last_write_checksum: sha1:e18d4e05e801ff5b52414fc51e98e10f33cfbb04
@@ -8247,8 +8283,8 @@ trackedFiles:
pristine_git_object: 89560b566073785535643e694c112bedbd3db13d
src/autumn_sdk/invoices.py:
id: 25223c7e4c52
- last_write_checksum: sha1:0c832df7a053da10c5ff73de6ba21f097ce0c4d6
- pristine_git_object: 4fb0916d1fabed241ee2f69fa741a9fd1df2c105
+ last_write_checksum: sha1:356e23073f95fb37849eb9c4474b482b2625356e
+ pristine_git_object: cc3243d2f49ce8e6645a9fe2d523a5308c267fb0
src/autumn_sdk/keys.py:
id: 078ab2a6d01a
last_write_checksum: sha1:809ab7ca51e2ebfead59fc15c2712747f74f38b0
@@ -8259,8 +8295,8 @@ trackedFiles:
pristine_git_object: ab7599187acf4fbd6a77ff0288d4ed12ea3d55ba
src/autumn_sdk/models/__init__.py:
id: bcf3802243ff
- last_write_checksum: sha1:9c03cd23656a890c1f5257a35aeb9e6a8d74a462
- pristine_git_object: 399430441a1741142bb239d2e6dc484af379534a
+ last_write_checksum: sha1:5e93dc1424597a9bfb1845f7010525c7d19f462a
+ pristine_git_object: 8cdb542a7efa7e90eed06e0d3f6867cd7b25564a
src/autumn_sdk/models/aggregateeventsop.py:
id: 01321099f2a5
last_write_checksum: sha1:acf521584ece8e3e217b02ec157d7ed43b783f21
@@ -8271,8 +8307,8 @@ trackedFiles:
pristine_git_object: 46847dd0edc83a5f2c950765d7f4e3e3190ba2ed
src/autumn_sdk/models/attachop.py:
id: ebb59e06476c
- last_write_checksum: sha1:40481bf405ad0b6cf8fcb37d21ee2d277484e80a
- pristine_git_object: 53c94f0f20197c041873fca4ab861bc7bccc0b39
+ last_write_checksum: sha1:20ee6cf084b6f90792a00776c1047f3eaa5c1913
+ pristine_git_object: e5980dcd883d2c58b32ce2ee475dd17a9b463a6d
src/autumn_sdk/models/balance.py:
id: a6354d7c4b97
last_write_checksum: sha1:b5ce91bd9a42de2332529dd64e83aa1154fd3d0e
@@ -8283,8 +8319,8 @@ trackedFiles:
pristine_git_object: 104214260539e267dba373b7f140bea00456c518
src/autumn_sdk/models/billingupdateop.py:
id: a2f17c75cfd3
- last_write_checksum: sha1:ff552cd8515f0b1fc35d592e368fc2b6b6bbd809
- pristine_git_object: 146135ee44a89e2c3499bb8fe885834972d97962
+ last_write_checksum: sha1:2881d25ca2192e9cd7cdc5a8d2b57a2d63b0b044
+ pristine_git_object: 155564345faa8f16c69c30d67f04a430c12210cf
src/autumn_sdk/models/checkop.py:
id: 31c2f84723c6
last_write_checksum: sha1:901e4f0ac685d3b1d551792d32fe913d0be2901e
@@ -8303,24 +8339,24 @@ trackedFiles:
pristine_git_object: 496a076933b3e87f7a049a22543d43e13b34ae56
src/autumn_sdk/models/createplanop.py:
id: 077e6c7db2ad
- last_write_checksum: sha1:8a283473602a5310940681839b74b235f522f33b
- pristine_git_object: 60a530501b933de009d348df432570b6d5bdd395
+ last_write_checksum: sha1:094db93bae2948a9a67a75f1aa023809da38d9b5
+ pristine_git_object: 328e6ee0a9733df5419801d49fa1232c666055b0
src/autumn_sdk/models/createreferralcodeop.py:
id: 2f5f7b136c39
last_write_checksum: sha1:9ef6a7e87ffd4cb0fb91f3592abb3b1ef51cb415
pristine_git_object: fea4d1bb3875848e66502705e4cb818233ec3f5a
src/autumn_sdk/models/createscheduleop.py:
id: afb0cf1cf7f2
- last_write_checksum: sha1:1a50c784e8c499e673702184d13c9648ce774abf
- pristine_git_object: 0d261c93c0c67f74f3e9e21d69af2f3ea089aff1
+ last_write_checksum: sha1:b04db551595734ec117319fa4156c68c059d8a83
+ pristine_git_object: 23bdb2f0183a1a374bf37b71c35be731e7b10bc4
src/autumn_sdk/models/customer.py:
id: 8ed0174f7272
last_write_checksum: sha1:30638da8b64e5ea23787fd1e50b8a051a2aa2031
pristine_git_object: dc08d3cbe2bc9b9f99612301d8534c0663511bbd
src/autumn_sdk/models/customerdata.py:
id: 9d88118f2123
- last_write_checksum: sha1:89e08ccf05d450a52f071d482c87c1bb5d2238a9
- pristine_git_object: 880d031cf79b1de8074472ce67c51e13b821c070
+ last_write_checksum: sha1:c09c1cdb9013fb0c13f7ad6a5bc1c9412d01c4f5
+ pristine_git_object: 5e26531d3899ee4b78eb5a53ab19cd7d24c1b9d6
src/autumn_sdk/models/customerexpand.py:
id: 9d337d480baa
last_write_checksum: sha1:ea31eb539b3854d917a7d8fe5685f730b8577a1f
@@ -8363,12 +8399,12 @@ trackedFiles:
pristine_git_object: e5644620facf26204c3a512525886fed0054c447
src/autumn_sdk/models/getorcreatecustomerop.py:
id: acfac0d7be14
- last_write_checksum: sha1:7e1ada1ef743598d6a348031e191bada46a47d97
- pristine_git_object: 6c0acae13f607be68b72415882915f02a97df16e
+ last_write_checksum: sha1:e0791efc77a21f33d29522a5dc93be2c6f959256
+ pristine_git_object: 7a843b58495be022dc9efcda60f142b40ae060d4
src/autumn_sdk/models/getplanop.py:
id: 590fb77ac88d
- last_write_checksum: sha1:f8a3baac7d3dd49ce7c573ca1533af1599f4d544
- pristine_git_object: aa3169c8b82b012608caf6d61f68df5e9f61f29e
+ last_write_checksum: sha1:d5900ea3cf7c13f25bbe7125c42fe53dbd4b82c2
+ pristine_git_object: 5f3b4a9b114b33c33c190ed0cf063778a235819d
src/autumn_sdk/models/getrevenuecatkeysop.py:
id: 015155862a71
last_write_checksum: sha1:6b3f88a48b721093562ec59db3f6dae2540fa139
@@ -8377,6 +8413,10 @@ trackedFiles:
id: bd28bf9634d8
last_write_checksum: sha1:7bed106390df2e41d8f36efb6ef306843e19021b
pristine_git_object: f6891cf0ca781a2636a1edb361abaa72a6ce8705
+ src/autumn_sdk/models/insertinvoicesop.py:
+ id: f40dc5a707d2
+ last_write_checksum: sha1:753dbdc5c1ffbac7f80191b8310a7dacdbbdd920
+ pristine_git_object: c7ebbe6eff442c215aa718603113b99caed61bec
src/autumn_sdk/models/internal/__init__.py:
id: 2906fe7f2cde
last_write_checksum: sha1:1905b58b74ecc52346d8f5c24ded2b6d6e1dad4a
@@ -8407,12 +8447,12 @@ trackedFiles:
pristine_git_object: b5077df0f24113f9e7c5fae25e5fefd923b7ec85
src/autumn_sdk/models/listinvoicesop.py:
id: efd56888952f
- last_write_checksum: sha1:40d9b902025d9542a94c4ec0ac6abdf1a891ab63
- pristine_git_object: 66bfca14d664bd33afbeeb57ffe3f279e5a8ecba
+ last_write_checksum: sha1:500ea4117a1e76d3feb69b1df9a97886717a2b3d
+ pristine_git_object: 82723c21210d8b89229a1683d8230c9372afce43
src/autumn_sdk/models/listplansop.py:
id: fdf892c403f4
- last_write_checksum: sha1:13331888063ee99fa3f12d3d668f17b287c73178
- pristine_git_object: 0f2b19fd6d340499a5a0e103d32d167f598db7e4
+ last_write_checksum: sha1:4de1c87862550f9a036ecae6ee774848dd76dcf8
+ pristine_git_object: f58d48fbff6f78eb85fc2567890f1a1d698e1a7a
src/autumn_sdk/models/listrewardsop.py:
id: de90eebc7e95
last_write_checksum: sha1:270f334d9955ca547175b4c936ba0ac416002f20
@@ -8435,12 +8475,12 @@ trackedFiles:
pristine_git_object: 79ac016f241d82916ccc1aab7c7e3a7e3fb9aef1
src/autumn_sdk/models/plan.py:
id: f85c4e07540d
- last_write_checksum: sha1:f509e8d77e5c067ca337600c31047dceb28ec601
- pristine_git_object: 883f3c3c7fc99d0a34f6c133c83277b5ac8a7f6b
+ last_write_checksum: sha1:52e039025172080ab9259118740982381ba818e0
+ pristine_git_object: 0ffd9b3918b2a6b47237579d608cd80a998fdb68
src/autumn_sdk/models/previewattachop.py:
id: 2b361be4bfa8
- last_write_checksum: sha1:3f78e99797fcd65170eb9e72542ba8e8f9a89898
- pristine_git_object: 8dd937dd8a86c2f3896fbf87b8fc1d81081c314f
+ last_write_checksum: sha1:3287d894a35f49d8dbc0ddf7dbdd49538696d6c0
+ pristine_git_object: 6a6f1ad73793a408b692e7ae86884fe94bc0b987
src/autumn_sdk/models/previewmultiattachop.py:
id: 963ffcd646a4
last_write_checksum: sha1:13eae6db5aea44988a2cc6c1ee688fc53cc2dac0
@@ -8451,8 +8491,8 @@ trackedFiles:
pristine_git_object: 85872f73a69aba31c8cbcd5da7f9f59b254dde49
src/autumn_sdk/models/previewupdateop.py:
id: 081d5f08508d
- last_write_checksum: sha1:ba82a17b54dbd204f6bb2f77931dd7a6b19dbe8d
- pristine_git_object: 25e80afc47fdc9f1cc8d8effc07f900a1c83ce39
+ last_write_checksum: sha1:ed38b9a691984414d264e40732ea31c045fc301a
+ pristine_git_object: 0054391992a70d0cdd8ecf81d169b8691b75d36a
src/autumn_sdk/models/redeemreferralcodeop.py:
id: 0abd7bfae718
last_write_checksum: sha1:b1a584450f1f79e796dd755a9305dc30a07ed601
@@ -8479,8 +8519,8 @@ trackedFiles:
pristine_git_object: aa686dd6f85ae1e27450392fcfe02527adfe8e61
src/autumn_sdk/models/setuppaymentop.py:
id: 603339ee67e3
- last_write_checksum: sha1:edf22e8537f17f0309d4b685648cba918d83f724
- pristine_git_object: 8762148494eb5d0b501a6310dcce0080e9d90970
+ last_write_checksum: sha1:155e6225117748c2dfb538a802a58d8e151f8e0d
+ pristine_git_object: 999805c95feb931dbab0adfac1c91807eb4d182f
src/autumn_sdk/models/syncrevenuecatop.py:
id: faddfbfd1214
last_write_checksum: sha1:a2b90222b09b4ca95bc78d3573f5b17d4d50208b
@@ -8499,8 +8539,8 @@ trackedFiles:
pristine_git_object: 7709cff4a867ccbb2022899e6a6751542d8f3fae
src/autumn_sdk/models/updatecustomerop.py:
id: 28b9d5b59bae
- last_write_checksum: sha1:fb4e390d525214b69261761b657ac6c1134b75d2
- pristine_git_object: 2f9f14dd4cc1794f86c9b17fbaae3dc038691456
+ last_write_checksum: sha1:b938fff71d98513e74d46e78275ac9a13e99d7f8
+ pristine_git_object: 85750af20855b999c70ace333181143ad833498c
src/autumn_sdk/models/updateentityop.py:
id: a49305af1e2e
last_write_checksum: sha1:de10e55412a7a7b7dfb98c5e040353407eff2210
@@ -8511,12 +8551,12 @@ trackedFiles:
pristine_git_object: daa78399b45fcaeda3fbe5843677a6900c520d4d
src/autumn_sdk/models/updateplanop.py:
id: 753ddf45ca40
- last_write_checksum: sha1:68ec8316b2b7381728e3e6a8183ae4480aa1097b
- pristine_git_object: 7bfbabf54d964c70f55360e6417a43eba1d1fb98
+ last_write_checksum: sha1:495149262bcac87ff868712a66b2ed92c711b8d6
+ pristine_git_object: 23ff21a47df57a32885e4679db18dc1f15e310e3
src/autumn_sdk/plans.py:
id: cf1ebabb687c
- last_write_checksum: sha1:2cf01e1e6a7a24cc5c9934ed26278278b13f1df9
- pristine_git_object: bdc12bb6677bd7d3704d8e7e7b04d5b0b1180593
+ last_write_checksum: sha1:ae4517b5e4c271794210ce03397eed27fa3a1646
+ pristine_git_object: 3baac7b5e75376ef1dc5af7440470884c8681889
src/autumn_sdk/platform.py:
id: aee79240c441
last_write_checksum: sha1:2e80cdba550e5487a2ac16c063e24caf3f2b265f
@@ -9386,4 +9426,14 @@ examples:
responses:
"200":
application/json: {"list": [{"license_plan_id": "seat_plan", "parent_plan_id": "pro_plan", "license_plan_name": "Seat", "granted": 10, "usage": 3, "remaining": 7, "paid_quantity": 5}]}
+ insertInvoices:
+ speakeasy-default-insert-invoices:
+ parameters:
+ header:
+ x-api-version: "2.3.0"
+ requestBody:
+ application/json: {"invoices": [{"customer_id": "cus_123", "plan_ids": ["pro"], "stripe_id": "in_legacy_123", "processor_type": "stripe", "status": "paid", "total": 29.99, "amount_paid": 29.99, "refunded_amount": 0, "currency": "usd", "created_at": 1451606400000, "hosted_invoice_url": "https://billing.example.com/invoices/legacy-123"}]}
+ responses:
+ "200":
+ application/json: {"invoices": [{"plan_ids": ["pro"], "stripe_id": "in_legacy_123", "processor_type": "stripe", "status": "paid", "total": 29.99, "currency": "usd", "created_at": 1451606400000, "hosted_invoice_url": "https://billing.example.com/invoices/legacy-123", "id": "inv_2b3c4d5e6f7g8h", "customer_id": "cus_123", "amount_paid": 29.99, "refunded_amount": 0}]}
examplesVersion: 1.0.2
diff --git a/others/python-sdk/README.md b/others/python-sdk/README.md
index 4c94e5b21a..4857650aa5 100644
--- a/others/python-sdk/README.md
+++ b/others/python-sdk/README.md
@@ -300,6 +300,7 @@ Use this to permanently remove a feature. Note: features that are used in produc
### [Invoices](docs/sdks/invoices/README.md)
+* [insert](docs/sdks/invoices/README.md#insert) - Inserts or updates up to 500 historical invoices without reading or mutating the billing processor.
* [list](docs/sdks/invoices/README.md#list) - Lists invoices with cursor pagination and optional filters (customer, entity, status, processor). Pass `start_cursor: ""` (or omit) for the first page; use `next_cursor` from a prior response for subsequent pages.
### [Keys](docs/sdks/keys/README.md)
diff --git a/others/python-sdk/src/autumn_sdk/invoices.py b/others/python-sdk/src/autumn_sdk/invoices.py
index 4fb0916d1f..cc3243d2f4 100644
--- a/others/python-sdk/src/autumn_sdk/invoices.py
+++ b/others/python-sdk/src/autumn_sdk/invoices.py
@@ -5,10 +5,198 @@
from autumn_sdk._hooks import HookContext
from autumn_sdk.types import OptionalNullable, UNSET
from autumn_sdk.utils.unmarshal_json_response import unmarshal_json_response
-from typing import List, Mapping, Optional
+from typing import List, Mapping, Optional, Union
class Invoices(BaseSDK):
+ def insert(
+ self,
+ *,
+ invoices: Union[
+ List[models.InvoiceRequestBody], List[models.InvoiceRequestBodyTypedDict]
+ ],
+ retries: OptionalNullable[utils.RetryConfig] = UNSET,
+ server_url: Optional[str] = None,
+ timeout_ms: Optional[int] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ) -> models.InsertInvoicesResponse:
+ r"""Inserts or updates up to 500 historical invoices without reading or mutating the billing processor.
+
+ :param invoices: Invoices to insert or update, in response order.
+ :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
+ :param http_headers: Additional headers to set or replace on requests.
+ """
+ base_url = None
+ url_variables = None
+ if timeout_ms is None:
+ timeout_ms = self.sdk_configuration.timeout_ms
+
+ if server_url is not None:
+ base_url = server_url
+ else:
+ base_url = self._get_url(base_url, url_variables)
+
+ request = models.InsertInvoicesParams(
+ invoices=utils.get_pydantic_model(
+ invoices, List[models.InvoiceRequestBody]
+ ),
+ )
+
+ req = self._build_request(
+ method="POST",
+ path="/v1/invoices.insert",
+ base_url=base_url,
+ url_variables=url_variables,
+ request=request,
+ request_body_required=True,
+ request_has_path_params=False,
+ request_has_query_params=True,
+ user_agent_header="user-agent",
+ accept_header_value="application/json",
+ http_headers=http_headers,
+ _globals=models.InsertInvoicesGlobals(
+ x_api_version=self.sdk_configuration.globals.x_api_version,
+ ),
+ security=self.sdk_configuration.security,
+ get_serialized_body=lambda: utils.serialize_request_body(
+ request, False, False, "json", models.InsertInvoicesParams
+ ),
+ allow_empty_value=None,
+ timeout_ms=timeout_ms,
+ )
+
+ if retries == UNSET:
+ if self.sdk_configuration.retry_config is not UNSET:
+ retries = self.sdk_configuration.retry_config
+
+ retry_config = None
+ if isinstance(retries, utils.RetryConfig):
+ retry_config = (retries, ["429", "500", "502", "503", "504"])
+
+ http_res = self.do_request(
+ hook_ctx=HookContext(
+ config=self.sdk_configuration,
+ base_url=base_url or "",
+ operation_id="insertInvoices",
+ oauth2_scopes=None,
+ security_source=self.sdk_configuration.security,
+ ),
+ request=req,
+ is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
+ retry_config=retry_config,
+ )
+
+ if utils.match_response(http_res, "200", "application/json"):
+ return unmarshal_json_response(models.InsertInvoicesResponse, http_res)
+ if utils.match_response(http_res, "4XX", "*"):
+ http_res_text = utils.stream_to_text(http_res)
+ raise errors.AutumnDefaultError(
+ "API error occurred", http_res, http_res_text
+ )
+ if utils.match_response(http_res, "5XX", "*"):
+ http_res_text = utils.stream_to_text(http_res)
+ raise errors.AutumnDefaultError(
+ "API error occurred", http_res, http_res_text
+ )
+
+ raise errors.AutumnDefaultError("Unexpected response received", http_res)
+
+ async def insert_async(
+ self,
+ *,
+ invoices: Union[
+ List[models.InvoiceRequestBody], List[models.InvoiceRequestBodyTypedDict]
+ ],
+ retries: OptionalNullable[utils.RetryConfig] = UNSET,
+ server_url: Optional[str] = None,
+ timeout_ms: Optional[int] = None,
+ http_headers: Optional[Mapping[str, str]] = None,
+ ) -> models.InsertInvoicesResponse:
+ r"""Inserts or updates up to 500 historical invoices without reading or mutating the billing processor.
+
+ :param invoices: Invoices to insert or update, in response order.
+ :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
+ :param http_headers: Additional headers to set or replace on requests.
+ """
+ base_url = None
+ url_variables = None
+ if timeout_ms is None:
+ timeout_ms = self.sdk_configuration.timeout_ms
+
+ if server_url is not None:
+ base_url = server_url
+ else:
+ base_url = self._get_url(base_url, url_variables)
+
+ request = models.InsertInvoicesParams(
+ invoices=utils.get_pydantic_model(
+ invoices, List[models.InvoiceRequestBody]
+ ),
+ )
+
+ req = self._build_request_async(
+ method="POST",
+ path="/v1/invoices.insert",
+ base_url=base_url,
+ url_variables=url_variables,
+ request=request,
+ request_body_required=True,
+ request_has_path_params=False,
+ request_has_query_params=True,
+ user_agent_header="user-agent",
+ accept_header_value="application/json",
+ http_headers=http_headers,
+ _globals=models.InsertInvoicesGlobals(
+ x_api_version=self.sdk_configuration.globals.x_api_version,
+ ),
+ security=self.sdk_configuration.security,
+ get_serialized_body=lambda: utils.serialize_request_body(
+ request, False, False, "json", models.InsertInvoicesParams
+ ),
+ allow_empty_value=None,
+ timeout_ms=timeout_ms,
+ )
+
+ if retries == UNSET:
+ if self.sdk_configuration.retry_config is not UNSET:
+ retries = self.sdk_configuration.retry_config
+
+ retry_config = None
+ if isinstance(retries, utils.RetryConfig):
+ retry_config = (retries, ["429", "500", "502", "503", "504"])
+
+ http_res = await self.do_request_async(
+ hook_ctx=HookContext(
+ config=self.sdk_configuration,
+ base_url=base_url or "",
+ operation_id="insertInvoices",
+ oauth2_scopes=None,
+ security_source=self.sdk_configuration.security,
+ ),
+ request=req,
+ is_error_status_code=lambda c: utils.match_status_codes(["4XX", "5XX"], c),
+ retry_config=retry_config,
+ )
+
+ if utils.match_response(http_res, "200", "application/json"):
+ return unmarshal_json_response(models.InsertInvoicesResponse, http_res)
+ if utils.match_response(http_res, "4XX", "*"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ raise errors.AutumnDefaultError(
+ "API error occurred", http_res, http_res_text
+ )
+ if utils.match_response(http_res, "5XX", "*"):
+ http_res_text = await utils.stream_to_text_async(http_res)
+ raise errors.AutumnDefaultError(
+ "API error occurred", http_res, http_res_text
+ )
+
+ raise errors.AutumnDefaultError("Unexpected response received", http_res)
+
def list(
self,
*,
@@ -17,7 +205,7 @@ def list(
customer_id: Optional[str] = None,
entity_id: Optional[str] = None,
status: Optional[List[models.ListInvoicesStatus]] = None,
- processor_types: Optional[List[models.ProcessorTypeRequest]] = None,
+ processor_types: Optional[List[models.ListInvoicesProcessorTypeRequest]] = None,
retries: OptionalNullable[utils.RetryConfig] = UNSET,
server_url: Optional[str] = None,
timeout_ms: Optional[int] = None,
@@ -122,7 +310,7 @@ async def list_async(
customer_id: Optional[str] = None,
entity_id: Optional[str] = None,
status: Optional[List[models.ListInvoicesStatus]] = None,
- processor_types: Optional[List[models.ProcessorTypeRequest]] = None,
+ processor_types: Optional[List[models.ListInvoicesProcessorTypeRequest]] = None,
retries: OptionalNullable[utils.RetryConfig] = UNSET,
server_url: Optional[str] = None,
timeout_ms: Optional[int] = None,
diff --git a/others/python-sdk/src/autumn_sdk/models/__init__.py b/others/python-sdk/src/autumn_sdk/models/__init__.py
index 399430441a..8cdb542a7e 100644
--- a/others/python-sdk/src/autumn_sdk/models/__init__.py
+++ b/others/python-sdk/src/autumn_sdk/models/__init__.py
@@ -1539,6 +1539,21 @@
Link,
LinkTypedDict,
)
+ from .insertinvoicesop import (
+ InsertInvoicesGlobals,
+ InsertInvoicesGlobalsTypedDict,
+ InsertInvoicesInvoiceResponse,
+ InsertInvoicesInvoiceResponseTypedDict,
+ InsertInvoicesParams,
+ InsertInvoicesParamsTypedDict,
+ InsertInvoicesProcessorTypeRequest,
+ InsertInvoicesProcessorTypeResponse,
+ InsertInvoicesResponse,
+ InsertInvoicesResponseTypedDict,
+ InsertInvoicesStatus,
+ InvoiceRequestBody,
+ InvoiceRequestBodyTypedDict,
+ )
from .linkrevenuecatop import (
LinkRevenueCatEnv,
LinkRevenueCatGlobals,
@@ -1730,10 +1745,10 @@
ListInvoicesListTypedDict,
ListInvoicesParams,
ListInvoicesParamsTypedDict,
+ ListInvoicesProcessorTypeRequest,
ListInvoicesResponse,
ListInvoicesResponseTypedDict,
ListInvoicesStatus,
- ProcessorTypeRequest,
)
from .listplansop import (
ListPlansAddItemAdditionalCurrency,
@@ -3125,6 +3140,8 @@
Migration,
MigrationTypedDict,
PriceVariantInterval,
+ UpdateLicenseParent,
+ UpdateLicenseParentTypedDict,
UpdatePlanAdditionalCurrencyRequestBody,
UpdatePlanAdditionalCurrencyRequestBodyTypedDict,
UpdatePlanAdditionalCurrencyResponse,
@@ -4879,10 +4896,23 @@
"IncludedUsage1TypedDict",
"IncludedUsage2",
"IncludedUsage2TypedDict",
+ "InsertInvoicesGlobals",
+ "InsertInvoicesGlobalsTypedDict",
+ "InsertInvoicesInvoiceResponse",
+ "InsertInvoicesInvoiceResponseTypedDict",
+ "InsertInvoicesParams",
+ "InsertInvoicesParamsTypedDict",
+ "InsertInvoicesProcessorTypeRequest",
+ "InsertInvoicesProcessorTypeResponse",
+ "InsertInvoicesResponse",
+ "InsertInvoicesResponseTypedDict",
+ "InsertInvoicesStatus",
"Intent",
"IntervalVariantRemoveItemEnum1",
"IntervalVariantRemoveItemEnum2",
"Invoice",
+ "InvoiceRequestBody",
+ "InvoiceRequestBodyTypedDict",
"InvoiceTypedDict",
"Item",
"ItemAdditionalCurrency",
@@ -5071,6 +5101,7 @@
"ListInvoicesListTypedDict",
"ListInvoicesParams",
"ListInvoicesParamsTypedDict",
+ "ListInvoicesProcessorTypeRequest",
"ListInvoicesResponse",
"ListInvoicesResponseTypedDict",
"ListInvoicesStatus",
@@ -5939,7 +5970,6 @@
"PreviewUpdateUsageLineItemTypedDict",
"PriceVariantInterval",
"ProcessorType",
- "ProcessorTypeRequest",
"Processors",
"ProcessorsTypedDict",
"ProductDisplay1",
@@ -6421,6 +6451,8 @@
"UpdateFeatureResponseTypedDict",
"UpdateFeatureTypeRequestBody",
"UpdateFeatureTypeResponse",
+ "UpdateLicenseParent",
+ "UpdateLicenseParentTypedDict",
"UpdatePlanAdditionalCurrencyRequestBody",
"UpdatePlanAdditionalCurrencyRequestBodyTypedDict",
"UpdatePlanAdditionalCurrencyResponse",
@@ -8222,6 +8254,19 @@
"ImportType": ".importop",
"Link": ".importop",
"LinkTypedDict": ".importop",
+ "InsertInvoicesGlobals": ".insertinvoicesop",
+ "InsertInvoicesGlobalsTypedDict": ".insertinvoicesop",
+ "InsertInvoicesInvoiceResponse": ".insertinvoicesop",
+ "InsertInvoicesInvoiceResponseTypedDict": ".insertinvoicesop",
+ "InsertInvoicesParams": ".insertinvoicesop",
+ "InsertInvoicesParamsTypedDict": ".insertinvoicesop",
+ "InsertInvoicesProcessorTypeRequest": ".insertinvoicesop",
+ "InsertInvoicesProcessorTypeResponse": ".insertinvoicesop",
+ "InsertInvoicesResponse": ".insertinvoicesop",
+ "InsertInvoicesResponseTypedDict": ".insertinvoicesop",
+ "InsertInvoicesStatus": ".insertinvoicesop",
+ "InvoiceRequestBody": ".insertinvoicesop",
+ "InvoiceRequestBodyTypedDict": ".insertinvoicesop",
"LinkRevenueCatEnv": ".linkrevenuecatop",
"LinkRevenueCatGlobals": ".linkrevenuecatop",
"LinkRevenueCatGlobalsTypedDict": ".linkrevenuecatop",
@@ -8402,10 +8447,10 @@
"ListInvoicesListTypedDict": ".listinvoicesop",
"ListInvoicesParams": ".listinvoicesop",
"ListInvoicesParamsTypedDict": ".listinvoicesop",
+ "ListInvoicesProcessorTypeRequest": ".listinvoicesop",
"ListInvoicesResponse": ".listinvoicesop",
"ListInvoicesResponseTypedDict": ".listinvoicesop",
"ListInvoicesStatus": ".listinvoicesop",
- "ProcessorTypeRequest": ".listinvoicesop",
"ListPlansAddItemAdditionalCurrency": ".listplansop",
"ListPlansAddItemAdditionalCurrencyTypedDict": ".listplansop",
"ListPlansAddItemBillingMethod": ".listplansop",
@@ -9748,6 +9793,8 @@
"Migration": ".updateplanop",
"MigrationTypedDict": ".updateplanop",
"PriceVariantInterval": ".updateplanop",
+ "UpdateLicenseParent": ".updateplanop",
+ "UpdateLicenseParentTypedDict": ".updateplanop",
"UpdatePlanAdditionalCurrencyRequestBody": ".updateplanop",
"UpdatePlanAdditionalCurrencyRequestBodyTypedDict": ".updateplanop",
"UpdatePlanAdditionalCurrencyResponse": ".updateplanop",
diff --git a/others/python-sdk/src/autumn_sdk/models/attachop.py b/others/python-sdk/src/autumn_sdk/models/attachop.py
index 53c94f0f20..e5980dcd88 100644
--- a/others/python-sdk/src/autumn_sdk/models/attachop.py
+++ b/others/python-sdk/src/autumn_sdk/models/attachop.py
@@ -1127,7 +1127,7 @@ def serialize_model(self, handler):
class AttachPurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: AttachPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1135,10 +1135,12 @@ class AttachPurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class AttachPurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: AttachPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1149,9 +1151,12 @@ class AttachPurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -1176,7 +1181,7 @@ class AttachAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[AttachPurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -1195,7 +1200,7 @@ class AttachAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[AttachPurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/billingupdateop.py b/others/python-sdk/src/autumn_sdk/models/billingupdateop.py
index 146135ee44..155564345f 100644
--- a/others/python-sdk/src/autumn_sdk/models/billingupdateop.py
+++ b/others/python-sdk/src/autumn_sdk/models/billingupdateop.py
@@ -1141,7 +1141,7 @@ def serialize_model(self, handler):
class BillingUpdatePurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: BillingUpdatePurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1149,10 +1149,12 @@ class BillingUpdatePurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class BillingUpdatePurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: BillingUpdatePurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1163,9 +1165,12 @@ class BillingUpdatePurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -1190,7 +1195,7 @@ class BillingUpdateAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[BillingUpdatePurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -1209,7 +1214,7 @@ class BillingUpdateAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[BillingUpdatePurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/createplanop.py b/others/python-sdk/src/autumn_sdk/models/createplanop.py
index 60a530501b..328e6ee0a9 100644
--- a/others/python-sdk/src/autumn_sdk/models/createplanop.py
+++ b/others/python-sdk/src/autumn_sdk/models/createplanop.py
@@ -1158,8 +1158,6 @@ class CreatePlanLicenseTypedDict(TypedDict):
prepaid_only: NotRequired[bool]
customize: NotRequired[Nullable[CreatePlanLicenseCustomizeTypedDict]]
metadata: NotRequired[Dict[str, Any]]
- version: NotRequired[int]
- r"""Pin the link to a specific version of the license plan. Omitted, an existing link keeps its pinned version and a new link resolves to the latest."""
class CreatePlanLicense(BaseModel):
@@ -1173,14 +1171,9 @@ class CreatePlanLicense(BaseModel):
metadata: Optional[Dict[str, Any]] = None
- version: Optional[int] = None
- r"""Pin the link to a specific version of the license plan. Omitted, an existing link keeps its pinned version and a new link resolves to the latest."""
-
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(
- ["included", "prepaid_only", "customize", "metadata", "version"]
- )
+ optional_fields = set(["included", "prepaid_only", "customize", "metadata"])
nullable_fields = set(["customize"])
serialized = handler(self)
m = {}
@@ -1304,7 +1297,7 @@ def serialize_model(self, handler):
class CreatePlanPurchaseLimitRequestTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: CreatePlanPurchaseLimitIntervalRequestBody
r"""The time interval for the purchase limit window."""
@@ -1312,10 +1305,12 @@ class CreatePlanPurchaseLimitRequestTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class CreatePlanPurchaseLimitRequest(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: CreatePlanPurchaseLimitIntervalRequestBody
r"""The time interval for the purchase limit window."""
@@ -1326,9 +1321,12 @@ class CreatePlanPurchaseLimitRequest(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -1353,7 +1351,7 @@ class CreatePlanAutoTopupRequestTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[CreatePlanPurchaseLimitRequestTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -1372,7 +1370,7 @@ class CreatePlanAutoTopupRequest(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[CreatePlanPurchaseLimitRequest] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -3222,7 +3220,7 @@ def serialize_model(self, handler):
class CreatePlanVariantDetailsPurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: CreatePlanVariantDetailsPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -3230,10 +3228,12 @@ class CreatePlanVariantDetailsPurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class CreatePlanVariantDetailsPurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: CreatePlanVariantDetailsPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -3244,9 +3244,12 @@ class CreatePlanVariantDetailsPurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -3271,7 +3274,7 @@ class CreatePlanVariantDetailsAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[CreatePlanVariantDetailsPurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -3290,7 +3293,7 @@ class CreatePlanVariantDetailsAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[CreatePlanVariantDetailsPurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/createscheduleop.py b/others/python-sdk/src/autumn_sdk/models/createscheduleop.py
index 0d261c93c0..23bdb2f018 100644
--- a/others/python-sdk/src/autumn_sdk/models/createscheduleop.py
+++ b/others/python-sdk/src/autumn_sdk/models/createscheduleop.py
@@ -1148,7 +1148,7 @@ def serialize_model(self, handler):
class CreateSchedulePurchaseLimit2TypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: CreateSchedulePurchaseLimitInterval2
r"""The time interval for the purchase limit window."""
@@ -1156,10 +1156,12 @@ class CreateSchedulePurchaseLimit2TypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class CreateSchedulePurchaseLimit2(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: CreateSchedulePurchaseLimitInterval2
r"""The time interval for the purchase limit window."""
@@ -1170,9 +1172,12 @@ class CreateSchedulePurchaseLimit2(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -1197,7 +1202,7 @@ class CreateScheduleAutoTopup2TypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[CreateSchedulePurchaseLimit2TypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -1216,7 +1221,7 @@ class CreateScheduleAutoTopup2(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[CreateSchedulePurchaseLimit2] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/customerdata.py b/others/python-sdk/src/autumn_sdk/models/customerdata.py
index 880d031cf7..5e26531d38 100644
--- a/others/python-sdk/src/autumn_sdk/models/customerdata.py
+++ b/others/python-sdk/src/autumn_sdk/models/customerdata.py
@@ -24,7 +24,7 @@
class CustomerDataPurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: CustomerDataPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -32,10 +32,12 @@ class CustomerDataPurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class CustomerDataPurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: CustomerDataPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -46,9 +48,12 @@ class CustomerDataPurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -73,7 +78,7 @@ class CustomerDataAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[CustomerDataPurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -92,7 +97,7 @@ class CustomerDataAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[CustomerDataPurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/getorcreatecustomerop.py b/others/python-sdk/src/autumn_sdk/models/getorcreatecustomerop.py
index 6c0acae13f..7a843b5849 100644
--- a/others/python-sdk/src/autumn_sdk/models/getorcreatecustomerop.py
+++ b/others/python-sdk/src/autumn_sdk/models/getorcreatecustomerop.py
@@ -54,7 +54,7 @@ def serialize_model(self, handler):
class GetOrCreateCustomerPurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: GetOrCreateCustomerPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -62,10 +62,12 @@ class GetOrCreateCustomerPurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class GetOrCreateCustomerPurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: GetOrCreateCustomerPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -76,9 +78,12 @@ class GetOrCreateCustomerPurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -103,7 +108,7 @@ class GetOrCreateCustomerAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[GetOrCreateCustomerPurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -122,7 +127,7 @@ class GetOrCreateCustomerAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[GetOrCreateCustomerPurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/getplanop.py b/others/python-sdk/src/autumn_sdk/models/getplanop.py
index aa3169c8b8..5f3b4a9b11 100644
--- a/others/python-sdk/src/autumn_sdk/models/getplanop.py
+++ b/others/python-sdk/src/autumn_sdk/models/getplanop.py
@@ -1484,7 +1484,7 @@ def serialize_model(self, handler):
class GetPlanVariantDetailsPurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: GetPlanVariantDetailsPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1492,10 +1492,12 @@ class GetPlanVariantDetailsPurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class GetPlanVariantDetailsPurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: GetPlanVariantDetailsPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1506,9 +1508,12 @@ class GetPlanVariantDetailsPurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -1533,7 +1538,7 @@ class GetPlanVariantDetailsAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[GetPlanVariantDetailsPurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -1552,7 +1557,7 @@ class GetPlanVariantDetailsAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[GetPlanVariantDetailsPurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/insertinvoicesop.py b/others/python-sdk/src/autumn_sdk/models/insertinvoicesop.py
new file mode 100644
index 0000000000..c7ebbe6eff
--- /dev/null
+++ b/others/python-sdk/src/autumn_sdk/models/insertinvoicesop.py
@@ -0,0 +1,279 @@
+"""Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
+
+from __future__ import annotations
+from autumn_sdk.types import (
+ BaseModel,
+ Nullable,
+ OptionalNullable,
+ UNSET,
+ UNSET_SENTINEL,
+ UnrecognizedStr,
+)
+from autumn_sdk.utils import FieldMetadata, HeaderMetadata
+import pydantic
+from pydantic import model_serializer
+from typing import List, Literal, Optional, Union
+from typing_extensions import Annotated, NotRequired, TypedDict
+
+
+class InsertInvoicesGlobalsTypedDict(TypedDict):
+ x_api_version: NotRequired[str]
+
+
+class InsertInvoicesGlobals(BaseModel):
+ x_api_version: Annotated[
+ Optional[str],
+ pydantic.Field(alias="x-api-version"),
+ FieldMetadata(header=HeaderMetadata(style="simple", explode=False)),
+ ] = "2.3.0"
+
+ @model_serializer(mode="wrap")
+ def serialize_model(self, handler):
+ optional_fields = set(["x-api-version"])
+ 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))
+
+ if val != UNSET_SENTINEL:
+ if val is not None or k not in optional_fields:
+ m[k] = val
+
+ return m
+
+
+InsertInvoicesProcessorTypeRequest = Literal[
+ "stripe",
+ "revenuecat",
+]
+r"""The billing processor that owns this invoice."""
+
+
+InsertInvoicesStatus = Literal[
+ "draft",
+ "open",
+ "void",
+ "paid",
+ "uncollectible",
+]
+r"""The invoice status."""
+
+
+class InvoiceRequestBodyTypedDict(TypedDict):
+ customer_id: str
+ r"""The customer this invoice belongs to."""
+ stripe_id: str
+ r"""The processor's stable invoice ID."""
+ status: InsertInvoicesStatus
+ r"""The invoice status."""
+ total: float
+ r"""The invoice total in major currency units."""
+ created_at: float
+ r"""The invoice creation timestamp in milliseconds."""
+ plan_ids: NotRequired[List[str]]
+ r"""Plan IDs represented by this invoice."""
+ processor_type: NotRequired[InsertInvoicesProcessorTypeRequest]
+ r"""The billing processor that owns this invoice."""
+ amount_paid: NotRequired[Nullable[float]]
+ r"""The amount paid in major currency units."""
+ refunded_amount: NotRequired[float]
+ r"""The refunded amount in major currency units."""
+ currency: NotRequired[str]
+ r"""The currency code. Defaults to the organization's default currency."""
+ hosted_invoice_url: NotRequired[Nullable[str]]
+ r"""The hosted invoice URL, when available."""
+
+
+class InvoiceRequestBody(BaseModel):
+ customer_id: str
+ r"""The customer this invoice belongs to."""
+
+ stripe_id: str
+ r"""The processor's stable invoice ID."""
+
+ status: InsertInvoicesStatus
+ r"""The invoice status."""
+
+ total: float
+ r"""The invoice total in major currency units."""
+
+ created_at: float
+ r"""The invoice creation timestamp in milliseconds."""
+
+ plan_ids: Optional[List[str]] = None
+ r"""Plan IDs represented by this invoice."""
+
+ processor_type: Optional[InsertInvoicesProcessorTypeRequest] = "stripe"
+ r"""The billing processor that owns this invoice."""
+
+ amount_paid: OptionalNullable[float] = UNSET
+ r"""The amount paid in major currency units."""
+
+ refunded_amount: Optional[float] = 0
+ r"""The refunded amount in major currency units."""
+
+ currency: Optional[str] = None
+ r"""The currency code. Defaults to the organization's default currency."""
+
+ hosted_invoice_url: OptionalNullable[str] = UNSET
+ r"""The hosted invoice URL, when available."""
+
+ @model_serializer(mode="wrap")
+ def serialize_model(self, handler):
+ optional_fields = set(
+ [
+ "plan_ids",
+ "processor_type",
+ "amount_paid",
+ "refunded_amount",
+ "currency",
+ "hosted_invoice_url",
+ ]
+ )
+ nullable_fields = set(["amount_paid", "hosted_invoice_url"])
+ 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))
+ 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
+ or is_nullable_and_explicitly_set
+ ):
+ m[k] = val
+
+ return m
+
+
+class InsertInvoicesParamsTypedDict(TypedDict):
+ invoices: List[InvoiceRequestBodyTypedDict]
+ r"""Invoices to insert or update, in response order."""
+
+
+class InsertInvoicesParams(BaseModel):
+ invoices: List[InvoiceRequestBody]
+ r"""Invoices to insert or update, in response order."""
+
+
+InsertInvoicesProcessorTypeResponse = Union[
+ Literal[
+ "stripe",
+ "revenuecat",
+ ],
+ UnrecognizedStr,
+]
+r"""The billing processor that owns this invoice."""
+
+
+class InsertInvoicesInvoiceResponseTypedDict(TypedDict):
+ plan_ids: List[str]
+ r"""Array of plan IDs included in this invoice"""
+ stripe_id: str
+ r"""The Stripe invoice ID"""
+ status: str
+ r"""The status of the invoice"""
+ total: float
+ r"""The total amount of the invoice"""
+ currency: str
+ r"""The currency code for the invoice"""
+ created_at: float
+ r"""Timestamp when the invoice was created"""
+ id: str
+ r"""The Autumn invoice ID."""
+ customer_id: str
+ r"""The customer this invoice belongs to."""
+ amount_paid: Nullable[float]
+ r"""The amount paid in major currency units."""
+ refunded_amount: float
+ r"""The refunded amount in major currency units."""
+ processor_type: NotRequired[InsertInvoicesProcessorTypeResponse]
+ r"""The billing processor that owns this invoice."""
+ hosted_invoice_url: NotRequired[Nullable[str]]
+ r"""URL to the Stripe-hosted invoice page"""
+
+
+class InsertInvoicesInvoiceResponse(BaseModel):
+ plan_ids: List[str]
+ r"""Array of plan IDs included in this invoice"""
+
+ stripe_id: str
+ r"""The Stripe invoice ID"""
+
+ status: str
+ r"""The status of the invoice"""
+
+ total: float
+ r"""The total amount of the invoice"""
+
+ currency: str
+ r"""The currency code for the invoice"""
+
+ created_at: float
+ r"""Timestamp when the invoice was created"""
+
+ id: str
+ r"""The Autumn invoice ID."""
+
+ customer_id: str
+ r"""The customer this invoice belongs to."""
+
+ amount_paid: Nullable[float]
+ r"""The amount paid in major currency units."""
+
+ refunded_amount: float
+ r"""The refunded amount in major currency units."""
+
+ processor_type: Optional[InsertInvoicesProcessorTypeResponse] = "stripe"
+ r"""The billing processor that owns this invoice."""
+
+ hosted_invoice_url: OptionalNullable[str] = UNSET
+ r"""URL to the Stripe-hosted invoice page"""
+
+ @model_serializer(mode="wrap")
+ def serialize_model(self, handler):
+ optional_fields = set(["processor_type", "hosted_invoice_url"])
+ nullable_fields = set(["hosted_invoice_url", "amount_paid"])
+ 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))
+ 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
+ or is_nullable_and_explicitly_set
+ ):
+ m[k] = val
+
+ return m
+
+
+class InsertInvoicesResponseTypedDict(TypedDict):
+ r"""OK"""
+
+ invoices: List[InsertInvoicesInvoiceResponseTypedDict]
+ r"""Inserted or updated invoices in request order."""
+
+
+class InsertInvoicesResponse(BaseModel):
+ r"""OK"""
+
+ invoices: List[InsertInvoicesInvoiceResponse]
+ r"""Inserted or updated invoices in request order."""
diff --git a/others/python-sdk/src/autumn_sdk/models/listinvoicesop.py b/others/python-sdk/src/autumn_sdk/models/listinvoicesop.py
index 66bfca14d6..82723c2121 100644
--- a/others/python-sdk/src/autumn_sdk/models/listinvoicesop.py
+++ b/others/python-sdk/src/autumn_sdk/models/listinvoicesop.py
@@ -53,7 +53,7 @@ def serialize_model(self, handler):
]
-ProcessorTypeRequest = Literal[
+ListInvoicesProcessorTypeRequest = Literal[
"stripe",
"revenuecat",
]
@@ -70,7 +70,7 @@ class ListInvoicesParamsTypedDict(TypedDict):
r"""Filter invoices to a single entity by ID. Must be provided together with customer_id, since entity IDs are only unique per customer."""
status: NotRequired[List[ListInvoicesStatus]]
r"""Filter by invoice status (draft, open, paid, void, uncollectible)."""
- processor_types: NotRequired[List[ProcessorTypeRequest]]
+ processor_types: NotRequired[List[ListInvoicesProcessorTypeRequest]]
r"""Filter by billing processor (stripe, revenuecat). Invoices recorded before processor tracking count as stripe."""
@@ -90,7 +90,7 @@ class ListInvoicesParams(BaseModel):
status: Optional[List[ListInvoicesStatus]] = None
r"""Filter by invoice status (draft, open, paid, void, uncollectible)."""
- processor_types: Optional[List[ProcessorTypeRequest]] = None
+ processor_types: Optional[List[ListInvoicesProcessorTypeRequest]] = None
r"""Filter by billing processor (stripe, revenuecat). Invoices recorded before processor tracking count as stripe."""
@model_serializer(mode="wrap")
diff --git a/others/python-sdk/src/autumn_sdk/models/listplansop.py b/others/python-sdk/src/autumn_sdk/models/listplansop.py
index 0f2b19fd6d..f58d48fbff 100644
--- a/others/python-sdk/src/autumn_sdk/models/listplansop.py
+++ b/others/python-sdk/src/autumn_sdk/models/listplansop.py
@@ -1450,7 +1450,7 @@ def serialize_model(self, handler):
class ListPlansVariantDetailsPurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: ListPlansVariantDetailsPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1458,10 +1458,12 @@ class ListPlansVariantDetailsPurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class ListPlansVariantDetailsPurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: ListPlansVariantDetailsPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1472,9 +1474,12 @@ class ListPlansVariantDetailsPurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -1499,7 +1504,7 @@ class ListPlansVariantDetailsAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[ListPlansVariantDetailsPurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -1518,7 +1523,7 @@ class ListPlansVariantDetailsAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[ListPlansVariantDetailsPurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/plan.py b/others/python-sdk/src/autumn_sdk/models/plan.py
index 883f3c3c7f..0ffd9b3918 100644
--- a/others/python-sdk/src/autumn_sdk/models/plan.py
+++ b/others/python-sdk/src/autumn_sdk/models/plan.py
@@ -1417,7 +1417,7 @@ def serialize_model(self, handler):
class PlanVariantDetailsPurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: PlanVariantDetailsPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1425,10 +1425,12 @@ class PlanVariantDetailsPurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class PlanVariantDetailsPurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: PlanVariantDetailsPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1439,9 +1441,12 @@ class PlanVariantDetailsPurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -1466,7 +1471,7 @@ class PlanVariantDetailsAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[PlanVariantDetailsPurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -1485,7 +1490,7 @@ class PlanVariantDetailsAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[PlanVariantDetailsPurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/previewattachop.py b/others/python-sdk/src/autumn_sdk/models/previewattachop.py
index 8dd937dd8a..6a6f1ad737 100644
--- a/others/python-sdk/src/autumn_sdk/models/previewattachop.py
+++ b/others/python-sdk/src/autumn_sdk/models/previewattachop.py
@@ -1142,7 +1142,7 @@ def serialize_model(self, handler):
class PreviewAttachPurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: PreviewAttachPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1150,10 +1150,12 @@ class PreviewAttachPurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class PreviewAttachPurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: PreviewAttachPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1164,9 +1166,12 @@ class PreviewAttachPurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -1191,7 +1196,7 @@ class PreviewAttachAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[PreviewAttachPurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -1210,7 +1215,7 @@ class PreviewAttachAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[PreviewAttachPurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/previewupdateop.py b/others/python-sdk/src/autumn_sdk/models/previewupdateop.py
index 25e80afc47..0054391992 100644
--- a/others/python-sdk/src/autumn_sdk/models/previewupdateop.py
+++ b/others/python-sdk/src/autumn_sdk/models/previewupdateop.py
@@ -1142,7 +1142,7 @@ def serialize_model(self, handler):
class PreviewUpdatePurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: PreviewUpdatePurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1150,10 +1150,12 @@ class PreviewUpdatePurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class PreviewUpdatePurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: PreviewUpdatePurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1164,9 +1166,12 @@ class PreviewUpdatePurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -1191,7 +1196,7 @@ class PreviewUpdateAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[PreviewUpdatePurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -1210,7 +1215,7 @@ class PreviewUpdateAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[PreviewUpdatePurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/setuppaymentop.py b/others/python-sdk/src/autumn_sdk/models/setuppaymentop.py
index 8762148494..999805c95f 100644
--- a/others/python-sdk/src/autumn_sdk/models/setuppaymentop.py
+++ b/others/python-sdk/src/autumn_sdk/models/setuppaymentop.py
@@ -1138,7 +1138,7 @@ def serialize_model(self, handler):
class SetupPaymentPurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: SetupPaymentPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1146,10 +1146,12 @@ class SetupPaymentPurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class SetupPaymentPurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: SetupPaymentPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -1160,9 +1162,12 @@ class SetupPaymentPurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -1187,7 +1192,7 @@ class SetupPaymentAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[SetupPaymentPurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -1206,7 +1211,7 @@ class SetupPaymentAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[SetupPaymentPurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/updatecustomerop.py b/others/python-sdk/src/autumn_sdk/models/updatecustomerop.py
index 2f9f14dd4c..85750af208 100644
--- a/others/python-sdk/src/autumn_sdk/models/updatecustomerop.py
+++ b/others/python-sdk/src/autumn_sdk/models/updatecustomerop.py
@@ -56,7 +56,7 @@ def serialize_model(self, handler):
class UpdateCustomerPurchaseLimitRequestTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: UpdateCustomerPurchaseLimitIntervalRequestBody
r"""The time interval for the purchase limit window."""
@@ -64,10 +64,12 @@ class UpdateCustomerPurchaseLimitRequestTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class UpdateCustomerPurchaseLimitRequest(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: UpdateCustomerPurchaseLimitIntervalRequestBody
r"""The time interval for the purchase limit window."""
@@ -78,9 +80,12 @@ class UpdateCustomerPurchaseLimitRequest(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -105,7 +110,7 @@ class UpdateCustomerAutoTopupRequestTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[UpdateCustomerPurchaseLimitRequestTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -124,7 +129,7 @@ class UpdateCustomerAutoTopupRequest(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[UpdateCustomerPurchaseLimitRequest] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/models/updateplanop.py b/others/python-sdk/src/autumn_sdk/models/updateplanop.py
index 7bfbabf54d..23ff21a47d 100644
--- a/others/python-sdk/src/autumn_sdk/models/updateplanop.py
+++ b/others/python-sdk/src/autumn_sdk/models/updateplanop.py
@@ -1158,8 +1158,6 @@ class UpdatePlanLicenseTypedDict(TypedDict):
prepaid_only: NotRequired[bool]
customize: NotRequired[Nullable[UpdatePlanLicenseCustomizeTypedDict]]
metadata: NotRequired[Dict[str, Any]]
- version: NotRequired[int]
- r"""Pin the link to a specific version of the license plan. Omitted, an existing link keeps its pinned version and a new link resolves to the latest."""
class UpdatePlanLicense(BaseModel):
@@ -1173,14 +1171,9 @@ class UpdatePlanLicense(BaseModel):
metadata: Optional[Dict[str, Any]] = None
- version: Optional[int] = None
- r"""Pin the link to a specific version of the license plan. Omitted, an existing link keeps its pinned version and a new link resolves to the latest."""
-
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(
- ["included", "prepaid_only", "customize", "metadata", "version"]
- )
+ optional_fields = set(["included", "prepaid_only", "customize", "metadata"])
nullable_fields = set(["customize"])
serialized = handler(self)
m = {}
@@ -1304,7 +1297,7 @@ def serialize_model(self, handler):
class UpdatePlanPurchaseLimitRequestTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: UpdatePlanPurchaseLimitIntervalRequestBody
r"""The time interval for the purchase limit window."""
@@ -1312,10 +1305,12 @@ class UpdatePlanPurchaseLimitRequestTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class UpdatePlanPurchaseLimitRequest(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: UpdatePlanPurchaseLimitIntervalRequestBody
r"""The time interval for the purchase limit window."""
@@ -1326,9 +1321,12 @@ class UpdatePlanPurchaseLimitRequest(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -1353,7 +1351,7 @@ class UpdatePlanAutoTopupRequestTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[UpdatePlanPurchaseLimitRequestTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -1372,7 +1370,7 @@ class UpdatePlanAutoTopupRequest(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[UpdatePlanPurchaseLimitRequest] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -1703,6 +1701,17 @@ def serialize_model(self, handler):
return m
+class UpdateLicenseParentTypedDict(TypedDict):
+ plan_id: str
+ version: int
+
+
+class UpdateLicenseParent(BaseModel):
+ plan_id: str
+
+ version: int
+
+
PriceVariantInterval = Literal[
"one_off",
"week",
@@ -2333,7 +2342,7 @@ def serialize_model(self, handler):
class VariantPurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: VariantPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -2341,10 +2350,12 @@ class VariantPurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class VariantPurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: VariantPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -2355,9 +2366,12 @@ class VariantPurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -2382,7 +2396,7 @@ class VariantAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[VariantPurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -2401,7 +2415,7 @@ class VariantAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[VariantPurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -2886,6 +2900,8 @@ class UpdatePlanParamsTypedDict(TypedDict):
r"""Force versioning even when no customers exist. Mutually exclusive with disable_version."""
update_variant_ids: NotRequired[List[str]]
r"""Variant plan IDs to apply this update to. Empty or omitted means no propagation."""
+ update_license_parents: NotRequired[List[UpdateLicenseParentTypedDict]]
+ r"""Parent plan versions that should receive this license-plan update."""
variants: NotRequired[List[VariantTypedDict]]
r"""Additive variant updates for this base plan. Missing variants are created when name is provided."""
is_default: NotRequired[bool]
@@ -2955,6 +2971,9 @@ class UpdatePlanParams(BaseModel):
update_variant_ids: Optional[List[str]] = None
r"""Variant plan IDs to apply this update to. Empty or omitted means no propagation."""
+ update_license_parents: Optional[List[UpdateLicenseParent]] = None
+ r"""Parent plan versions that should receive this license-plan update."""
+
variants: Optional[List[Variant]] = None
r"""Additive variant updates for this base plan. Missing variants are created when name is provided."""
@@ -2987,6 +3006,7 @@ def serialize_model(self, handler):
"migration",
"force_version",
"update_variant_ids",
+ "update_license_parents",
"variants",
"is_default",
]
@@ -4447,7 +4467,7 @@ def serialize_model(self, handler):
class UpdatePlanVariantDetailsPurchaseLimitTypedDict(TypedDict):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: UpdatePlanVariantDetailsPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -4455,10 +4475,12 @@ class UpdatePlanVariantDetailsPurchaseLimitTypedDict(TypedDict):
r"""Maximum number of auto top-ups allowed within the interval."""
interval_count: NotRequired[float]
r"""Number of intervals in the purchase limit window."""
+ count: NotRequired[float]
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
class UpdatePlanVariantDetailsPurchaseLimit(BaseModel):
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
interval: UpdatePlanVariantDetailsPurchaseLimitInterval
r"""The time interval for the purchase limit window."""
@@ -4469,9 +4491,12 @@ class UpdatePlanVariantDetailsPurchaseLimit(BaseModel):
interval_count: Optional[float] = 1
r"""Number of intervals in the purchase limit window."""
+ count: Optional[float] = None
+ r"""Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged."""
+
@model_serializer(mode="wrap")
def serialize_model(self, handler):
- optional_fields = set(["interval_count"])
+ optional_fields = set(["interval_count", "count"])
serialized = handler(self)
m = {}
@@ -4496,7 +4521,7 @@ class UpdatePlanVariantDetailsAutoTopupTypedDict(TypedDict):
enabled: NotRequired[bool]
r"""Whether auto top-up is enabled."""
purchase_limit: NotRequired[UpdatePlanVariantDetailsPurchaseLimitTypedDict]
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: NotRequired[bool]
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
@@ -4515,7 +4540,7 @@ class UpdatePlanVariantDetailsAutoTopup(BaseModel):
r"""Whether auto top-up is enabled."""
purchase_limit: Optional[UpdatePlanVariantDetailsPurchaseLimit] = None
- r"""Optional rate limit to cap how often auto top-ups occur."""
+ r"""Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups."""
invoice_mode: Optional[bool] = None
r"""When true, auto top-up creates a send_invoice invoice instead of auto-charging."""
diff --git a/others/python-sdk/src/autumn_sdk/plans.py b/others/python-sdk/src/autumn_sdk/plans.py
index bdc12bb667..3baac7b5e7 100644
--- a/others/python-sdk/src/autumn_sdk/plans.py
+++ b/others/python-sdk/src/autumn_sdk/plans.py
@@ -789,6 +789,12 @@ def update(
migration: Optional[Union[models.Migration, models.MigrationTypedDict]] = None,
force_version: Optional[bool] = None,
update_variant_ids: Optional[List[str]] = None,
+ update_license_parents: Optional[
+ Union[
+ List[models.UpdateLicenseParent],
+ List[models.UpdateLicenseParentTypedDict],
+ ]
+ ] = None,
variants: Optional[
Union[List[models.Variant], List[models.VariantTypedDict]]
] = None,
@@ -827,6 +833,7 @@ def update(
:param migration:
:param force_version: Force versioning even when no customers exist. Mutually exclusive with disable_version.
:param update_variant_ids: Variant plan IDs to apply this update to. Empty or omitted means no propagation.
+ :param update_license_parents: Parent plan versions that should receive this license-plan update.
:param variants: Additive variant updates for this base plan. Missing variants are created when name is provided.
:param is_default: Whether this is the org's default plan. Cannot be true on a variant.
:param retries: Override the default retry configuration for this method
@@ -880,6 +887,9 @@ def update(
migration=utils.get_pydantic_model(migration, Optional[models.Migration]),
force_version=force_version,
update_variant_ids=update_variant_ids,
+ update_license_parents=utils.get_pydantic_model(
+ update_license_parents, Optional[List[models.UpdateLicenseParent]]
+ ),
variants=utils.get_pydantic_model(variants, Optional[List[models.Variant]]),
is_default=is_default,
)
@@ -997,6 +1007,12 @@ async def update_async(
migration: Optional[Union[models.Migration, models.MigrationTypedDict]] = None,
force_version: Optional[bool] = None,
update_variant_ids: Optional[List[str]] = None,
+ update_license_parents: Optional[
+ Union[
+ List[models.UpdateLicenseParent],
+ List[models.UpdateLicenseParentTypedDict],
+ ]
+ ] = None,
variants: Optional[
Union[List[models.Variant], List[models.VariantTypedDict]]
] = None,
@@ -1035,6 +1051,7 @@ async def update_async(
:param migration:
:param force_version: Force versioning even when no customers exist. Mutually exclusive with disable_version.
:param update_variant_ids: Variant plan IDs to apply this update to. Empty or omitted means no propagation.
+ :param update_license_parents: Parent plan versions that should receive this license-plan update.
:param variants: Additive variant updates for this base plan. Missing variants are created when name is provided.
:param is_default: Whether this is the org's default plan. Cannot be true on a variant.
:param retries: Override the default retry configuration for this method
@@ -1088,6 +1105,9 @@ async def update_async(
migration=utils.get_pydantic_model(migration, Optional[models.Migration]),
force_version=force_version,
update_variant_ids=update_variant_ids,
+ update_license_parents=utils.get_pydantic_model(
+ update_license_parents, Optional[List[models.UpdateLicenseParent]]
+ ),
variants=utils.get_pydantic_model(variants, Optional[List[models.Variant]]),
is_default=is_default,
)
diff --git a/others/python-sdk/uv.lock b/others/python-sdk/uv.lock
index 52537c7b1d..eb7efb819e 100644
--- a/others/python-sdk/uv.lock
+++ b/others/python-sdk/uv.lock
@@ -151,11 +151,11 @@ wheels = [
[[package]]
name = "idna"
-version = "3.11"
+version = "3.15"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" },
]
[[package]]
diff --git a/others/python-test/uv.lock b/others/python-test/uv.lock
index ab4394a675..6e17324f14 100644
--- a/others/python-test/uv.lock
+++ b/others/python-test/uv.lock
@@ -25,7 +25,7 @@ wheels = [
[[package]]
name = "autumn-sdk"
-version = "1.0.0"
+version = "0.4.18"
source = { editable = "../python-sdk" }
dependencies = [
{ name = "httpcore" },
@@ -106,11 +106,11 @@ wheels = [
[[package]]
name = "idna"
-version = "3.11"
+version = "3.15"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" },
]
[[package]]
diff --git a/package.json b/package.json
index bfcf6487d4..94566909ba 100644
--- a/package.json
+++ b/package.json
@@ -130,6 +130,7 @@
"d:test": "ENV_FILE=.env infisical run --env=test --recursive -- bun scripts/dev.ts",
"p": "ENV_FILE=.env.prod infisical run --env=prod --recursive -- bun scripts/dev.ts",
"s": "ENV_FILE=.env.staging infisical run --env=staging --recursive -- bun scripts/dev.ts",
+ "reset-v2": "bun server/src/cron/resetV2.ts",
"l": "bash ./scripts/dev-local.sh",
"setup": "node scripts/setup/setup.js",
"contracts": "infisical run --env=dev --recursive -- bun scripts/s3/contracts.ts",
diff --git a/packages/autumn-js/src/generated/attachSchemas.ts b/packages/autumn-js/src/generated/attachSchemas.ts
index 50efbb3062..57e016ba10 100644
--- a/packages/autumn-js/src/generated/attachSchemas.ts
+++ b/packages/autumn-js/src/generated/attachSchemas.ts
@@ -326,6 +326,7 @@ export const attachPurchaseLimitOutboundSchema = z.object({
interval: z.string(),
interval_count: z.number(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const attachAutoTopupOutboundSchema = z.object({
@@ -803,6 +804,7 @@ export const attachPurchaseLimitSchema = z.object({
interval: attachPurchaseLimitIntervalSchema,
intervalCount: z.union([z.number(), z.undefined()]).optional(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const attachAutoTopupSchema = z.object({
diff --git a/packages/autumn-js/src/generated/getOrCreateCustomerSchemas.ts b/packages/autumn-js/src/generated/getOrCreateCustomerSchemas.ts
index ec9c9e8bd8..1041c185b3 100644
--- a/packages/autumn-js/src/generated/getOrCreateCustomerSchemas.ts
+++ b/packages/autumn-js/src/generated/getOrCreateCustomerSchemas.ts
@@ -32,6 +32,7 @@ export const getOrCreateCustomerPurchaseLimitOutboundSchema = z.object({
interval: z.string(),
interval_count: z.number(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const getOrCreateCustomerAutoTopupOutboundSchema = z.object({
@@ -157,6 +158,7 @@ export const getOrCreateCustomerPurchaseLimitSchema = z.object({
interval: getOrCreateCustomerPurchaseLimitIntervalSchema,
intervalCount: z.union([z.number(), z.undefined()]).optional(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const getOrCreateCustomerAutoTopupSchema = z.object({
diff --git a/packages/autumn-js/src/generated/listPlansSchemas.ts b/packages/autumn-js/src/generated/listPlansSchemas.ts
index 725abbb316..797c112da4 100644
--- a/packages/autumn-js/src/generated/listPlansSchemas.ts
+++ b/packages/autumn-js/src/generated/listPlansSchemas.ts
@@ -325,6 +325,7 @@ export const listPlansVariantDetailsPurchaseLimitSchema = z.object({
interval: listPlansVariantDetailsPurchaseLimitIntervalSchema,
intervalCount: z.number(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const listPlansVariantDetailsAutoTopupSchema = z.object({
diff --git a/packages/autumn-js/src/generated/previewAttachSchemas.ts b/packages/autumn-js/src/generated/previewAttachSchemas.ts
index f2f5b7f3be..517a438354 100644
--- a/packages/autumn-js/src/generated/previewAttachSchemas.ts
+++ b/packages/autumn-js/src/generated/previewAttachSchemas.ts
@@ -439,6 +439,7 @@ export const previewAttachPurchaseLimitOutboundSchema = z.object({
interval: z.string(),
interval_count: z.number(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const previewAttachAutoTopupOutboundSchema = z.object({
@@ -951,6 +952,7 @@ export const previewAttachPurchaseLimitSchema = z.object({
interval: previewAttachPurchaseLimitIntervalSchema,
intervalCount: z.union([z.number(), z.undefined()]).optional(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const previewAttachAutoTopupSchema = z.object({
diff --git a/packages/autumn-js/src/generated/previewUpdateSubscriptionSchemas.ts b/packages/autumn-js/src/generated/previewUpdateSubscriptionSchemas.ts
index 1e2786b1e0..fe8c006f0b 100644
--- a/packages/autumn-js/src/generated/previewUpdateSubscriptionSchemas.ts
+++ b/packages/autumn-js/src/generated/previewUpdateSubscriptionSchemas.ts
@@ -433,6 +433,7 @@ export const previewUpdatePurchaseLimitOutboundSchema = z.object({
interval: z.string(),
interval_count: z.number(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const previewUpdateAutoTopupOutboundSchema = z.object({
@@ -922,6 +923,7 @@ export const previewUpdatePurchaseLimitSchema = z.object({
interval: previewUpdatePurchaseLimitIntervalSchema,
intervalCount: z.union([z.number(), z.undefined()]).optional(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const previewUpdateAutoTopupSchema = z.object({
diff --git a/packages/autumn-js/src/generated/setupPaymentSchemas.ts b/packages/autumn-js/src/generated/setupPaymentSchemas.ts
index 70524a9d48..2708395b49 100644
--- a/packages/autumn-js/src/generated/setupPaymentSchemas.ts
+++ b/packages/autumn-js/src/generated/setupPaymentSchemas.ts
@@ -345,6 +345,7 @@ export const setupPaymentPurchaseLimitOutboundSchema = z.object({
interval: z.string(),
interval_count: z.number(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const setupPaymentAutoTopupOutboundSchema = z.object({
@@ -829,6 +830,7 @@ export const setupPaymentPurchaseLimitSchema = z.object({
interval: setupPaymentPurchaseLimitIntervalSchema,
intervalCount: z.union([z.number(), z.undefined()]).optional(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const setupPaymentAutoTopupSchema = z.object({
diff --git a/packages/autumn-js/src/generated/updateSubscriptionSchemas.ts b/packages/autumn-js/src/generated/updateSubscriptionSchemas.ts
index 2a6d6dc59f..d62753c373 100644
--- a/packages/autumn-js/src/generated/updateSubscriptionSchemas.ts
+++ b/packages/autumn-js/src/generated/updateSubscriptionSchemas.ts
@@ -348,6 +348,7 @@ export const billingUpdatePurchaseLimitOutboundSchema = z.object({
interval: z.string(),
interval_count: z.number(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const billingUpdateAutoTopupOutboundSchema = z.object({
@@ -832,6 +833,7 @@ export const billingUpdatePurchaseLimitSchema = z.object({
interval: billingUpdatePurchaseLimitIntervalSchema,
intervalCount: z.union([z.number(), z.undefined()]).optional(),
limit: z.number(),
+ count: z.union([z.number(), z.undefined()]).optional(),
});
export const billingUpdateAutoTopupSchema = z.object({
diff --git a/packages/mcp/src/tools/org.ts b/packages/mcp/src/tools/org.ts
index ab567d5e3d..8356141afa 100644
--- a/packages/mcp/src/tools/org.ts
+++ b/packages/mcp/src/tools/org.ts
@@ -4,13 +4,19 @@ import { getAutumnAuth } from "../server/auth/auth.js";
import { mcpAnnotations } from "./utils/annotations.js";
import { callAutumnGet } from "./utils/client.js";
-const organizationMeSchema = z
- .object({
- name: z.string(),
- slug: z.string(),
- env: z.string(),
- })
- .strict();
+const organizationMeSchema = z.object({
+ id: z.string(),
+ name: z.string(),
+ slug: z.string(),
+ env: z.string(),
+ user: z
+ .object({
+ id: z.string(),
+ email: z.string(),
+ name: z.string(),
+ })
+ .nullish(),
+});
const signalOf = (context: { mcp?: { extra?: { signal?: AbortSignal } } }) =>
context?.mcp?.extra?.signal;
diff --git a/packages/mcp/tests/unit/mcp-server/agent/tools.test.ts b/packages/mcp/tests/unit/mcp-server/agent/tools.test.ts
index 00c5b1a29a..2ccac48160 100644
--- a/packages/mcp/tests/unit/mcp-server/agent/tools.test.ts
+++ b/packages/mcp/tests/unit/mcp-server/agent/tools.test.ts
@@ -557,9 +557,15 @@ describe("Autumn operation tools", () => {
expect(init?.method).toBe("GET");
expect(init?.body).toBeUndefined();
return Response.json({
+ id: "org_unit_tests",
name: "Unit Tests",
slug: "unit-tests",
env: "sandbox",
+ user: {
+ id: "user_unit_tests",
+ email: "unit@tests.dev",
+ name: "Unit Tester",
+ },
});
}) as typeof fetch;
@@ -575,9 +581,15 @@ describe("Autumn operation tools", () => {
{ mcp: { extra: { authInfo: auth } } } as never,
),
).resolves.toEqual({
+ id: "org_unit_tests",
name: "Unit Tests",
slug: "unit-tests",
env: "sandbox",
+ user: {
+ id: "user_unit_tests",
+ email: "unit@tests.dev",
+ name: "Unit Tester",
+ },
});
} finally {
globalThis.fetch = originalFetch;
diff --git a/packages/openapi/openapi-stripped.yml b/packages/openapi/openapi-stripped.yml
index e3992d8763..a9e5c26f12 100644
--- a/packages/openapi/openapi-stripped.yml
+++ b/packages/openapi/openapi-stripped.yml
@@ -76,7 +76,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -103,10 +102,16 @@ components:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -339,7 +344,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -1996,7 +2000,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -2023,10 +2026,16 @@ components:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -2197,7 +2206,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -2831,7 +2839,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -2858,10 +2865,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -3160,7 +3173,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -4217,7 +4229,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -5075,7 +5086,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -5102,10 +5112,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -5341,7 +5357,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -6679,13 +6694,6 @@ paths:
propertyNames:
type: string
additionalProperties: {}
- version:
- type: integer
- minimum: 1
- maximum: 9007199254740991
- description: Pin the link to a specific version of the license plan. Omitted, an
- existing link keeps its pinned version and a new link
- resolves to the latest.
required:
- license_plan_id
description: Plans offered as assignable licenses under this plan. The full set
@@ -6748,7 +6756,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -6775,10 +6782,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -7740,7 +7753,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -7767,10 +7779,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed
+ top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -7944,7 +7963,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -9024,7 +9042,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -9051,10 +9068,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed
+ top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -9228,7 +9252,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -10314,7 +10337,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -10341,10 +10363,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's
+ consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -10519,7 +10548,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -11389,13 +11417,6 @@ paths:
propertyNames:
type: string
additionalProperties: {}
- version:
- type: integer
- minimum: 1
- maximum: 9007199254740991
- description: Pin the link to a specific version of the license plan. Omitted, an
- existing link keeps its pinned version and a new link
- resolves to the latest.
required:
- license_plan_id
description: Plans offered as assignable licenses under this plan. The full set
@@ -11461,7 +11482,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -11488,10 +11508,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -11689,6 +11715,23 @@ paths:
type: string
description: Variant plan IDs to apply this update to. Empty or omitted means no
propagation.
+ update_license_parents:
+ type: array
+ items:
+ type: object
+ properties:
+ plan_id:
+ type: string
+ minLength: 1
+ pattern: ^[a-zA-Z0-9_-]+$
+ version:
+ type: integer
+ minimum: 1
+ maximum: 9007199254740991
+ required:
+ - plan_id
+ - version
+ description: Parent plan versions that should receive this license-plan update.
variants:
type: array
items:
@@ -12044,7 +12087,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -12071,10 +12113,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed
+ top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -13029,7 +13078,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -13056,10 +13104,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed
+ top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -13233,7 +13288,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -15057,7 +15111,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -15084,10 +15137,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -16478,7 +16537,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -16505,10 +16563,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's
+ consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -17179,7 +17244,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -17206,10 +17270,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's
+ consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -18790,7 +18861,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -18817,10 +18887,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -21507,7 +21583,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -21534,10 +21609,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -22833,7 +22914,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -22860,10 +22940,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -25176,7 +25262,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -25203,10 +25288,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -27139,7 +27230,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -27916,7 +28006,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -29538,6 +29627,198 @@ paths:
x-speakeasy-name-override: aggregate
parameters:
- *a5
+ /v1/invoices.insert:
+ post:
+ operationId: insertInvoices
+ description: Inserts or updates up to 500 historical invoices without reading or
+ mutating the billing processor.
+ tags:
+ - invoices
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ invoices:
+ type: array
+ maxItems: 500
+ items:
+ type: object
+ properties:
+ customer_id:
+ type: string
+ description: The customer this invoice belongs to.
+ plan_ids:
+ type: array
+ items:
+ type: string
+ default: []
+ description: Plan IDs represented by this invoice.
+ stripe_id:
+ type: string
+ description: The processor's stable invoice ID.
+ processor_type:
+ enum:
+ - stripe
+ - revenuecat
+ type: string
+ default: stripe
+ description: The billing processor that owns this invoice.
+ status:
+ enum:
+ - draft
+ - open
+ - void
+ - paid
+ - uncollectible
+ type: string
+ description: The invoice status.
+ total:
+ type: number
+ description: The invoice total in major currency units.
+ amount_paid:
+ anyOf:
+ - type: number
+ - type: "null"
+ default: null
+ description: The amount paid in major currency units.
+ refunded_amount:
+ type: number
+ default: 0
+ description: The refunded amount in major currency units.
+ currency:
+ type: string
+ description: The currency code. Defaults to the organization's default currency.
+ created_at:
+ type: number
+ description: The invoice creation timestamp in milliseconds.
+ hosted_invoice_url:
+ anyOf:
+ - type: string
+ - type: "null"
+ default: null
+ description: The hosted invoice URL, when available.
+ required:
+ - customer_id
+ - stripe_id
+ - status
+ - total
+ - created_at
+ description: Invoices to insert or update, in response order.
+ required:
+ - invoices
+ title: InsertInvoicesParams
+ examples:
+ - &a72
+ invoices:
+ - customer_id: cus_123
+ plan_ids:
+ - pro
+ stripe_id: in_legacy_123
+ processor_type: stripe
+ status: paid
+ total: 29.99
+ amount_paid: 29.99
+ refunded_amount: 0
+ currency: usd
+ created_at: 1451606400000
+ hosted_invoice_url: https://billing.example.com/invoices/legacy-123
+ example: *a72
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ invoices:
+ type: array
+ items:
+ type: object
+ properties:
+ plan_ids:
+ type: array
+ items:
+ type: string
+ description: Array of plan IDs included in this invoice
+ stripe_id:
+ type: string
+ description: The Stripe invoice ID
+ processor_type:
+ enum:
+ - stripe
+ - revenuecat
+ type: string
+ default: stripe
+ description: The billing processor that owns this invoice.
+ status:
+ type: string
+ description: The status of the invoice
+ total:
+ type: number
+ description: The total amount of the invoice
+ currency:
+ type: string
+ description: The currency code for the invoice
+ created_at:
+ type: number
+ description: Timestamp when the invoice was created
+ hosted_invoice_url:
+ anyOf:
+ - type: string
+ - type: "null"
+ description: URL to the Stripe-hosted invoice page
+ id:
+ type: string
+ description: The Autumn invoice ID.
+ customer_id:
+ type: string
+ description: The customer this invoice belongs to.
+ amount_paid:
+ anyOf:
+ - type: number
+ - type: "null"
+ description: The amount paid in major currency units.
+ refunded_amount:
+ type: number
+ description: The refunded amount in major currency units.
+ required:
+ - plan_ids
+ - stripe_id
+ - status
+ - total
+ - currency
+ - created_at
+ - id
+ - customer_id
+ - amount_paid
+ - refunded_amount
+ description: Inserted or updated invoices in request order.
+ required:
+ - invoices
+ examples:
+ - &a73
+ invoices:
+ - id: inv_2b3c4d5e6f7g8h
+ customer_id: cus_123
+ plan_ids:
+ - pro
+ stripe_id: in_legacy_123
+ processor_type: stripe
+ status: paid
+ total: 29.99
+ amount_paid: 29.99
+ refunded_amount: 0
+ currency: usd
+ created_at: 1451606400000
+ hosted_invoice_url: https://billing.example.com/invoices/legacy-123
+ example: *a73
+ x-speakeasy-name-override: insert
+ parameters:
+ - *a5
/v1/invoices.list:
post:
operationId: listInvoices
@@ -29596,14 +29877,14 @@ paths:
before processor tracking count as stripe.
title: ListInvoicesParams
examples:
- - &a72
+ - &a74
start_cursor: ""
limit: 10
customer_id: cus_123
status:
- open
- paid
- example: *a72
+ example: *a74
responses:
"200":
description: OK
@@ -29695,7 +29976,7 @@ paths:
- list
- next_cursor
examples:
- - &a73
+ - &a75
list:
- id: inv_2b3c4d5e6f7g8h
customer_id: cus_123
@@ -29712,7 +29993,7 @@ paths:
created_at: 1759247877000
hosted_invoice_url: https://invoice.stripe.com/i/acct_123/test_456
next_cursor: null
- example: *a73
+ example: *a75
x-speakeasy-name-override: list
parameters:
- *a5
@@ -29759,13 +30040,13 @@ paths:
- entities
title: AttachLicenseParams
examples:
- - &a74
+ - &a76
customer_id: cus_123
plan_id: seat_plan
entities:
- entity_id: user_123
name: Ada Lovelace
- example: *a74
+ example: *a76
responses:
"200":
description: OK
@@ -29809,12 +30090,12 @@ paths:
- entity_ids
title: ReleaseLicenseParams
examples:
- - &a75
+ - &a77
customer_id: cus_123
license_plan_id: seat_plan
entity_ids:
- user_123
- example: *a75
+ example: *a77
responses:
"200":
description: OK
@@ -29856,10 +30137,10 @@ paths:
- customer_id
title: ListLicenseAssignmentsParams
examples:
- - &a76
+ - &a78
customer_id: cus_123
active: true
- example: *a76
+ example: *a78
responses:
"200":
description: OK
@@ -29894,14 +30175,14 @@ paths:
required:
- list
examples:
- - &a77
+ - &a79
list:
- id: lic_asn_123
entity_id: user_123
license_plan_id: seat_plan
started_at: 1759247877000
ended_at: null
- example: *a77
+ example: *a79
x-speakeasy-name-override: listAssignments
x-speakeasy-ignore: true
parameters:
@@ -29927,9 +30208,9 @@ paths:
- customer_id
title: ListLicensesParams
examples:
- - &a78
+ - &a80
customer_id: cus_123
- example: *a78
+ example: *a80
responses:
"200":
description: OK
@@ -29975,7 +30256,7 @@ paths:
required:
- list
examples:
- - &a79
+ - &a81
list:
- license_plan_id: seat_plan
parent_plan_id: pro_plan
@@ -29984,7 +30265,7 @@ paths:
usage: 3
remaining: 7
paid_quantity: 5
- example: *a79
+ example: *a81
x-speakeasy-name-override: list
x-speakeasy-ignore: true
parameters:
@@ -30170,12 +30451,12 @@ paths:
- entity_id
title: CreateEntityParams
examples:
- - &a80
+ - &a82
customer_id: cus_123
entity_id: seat_42
feature_id: seats
name: Seat 42
- example: *a80
+ example: *a82
responses:
"200":
description: OK
@@ -30706,7 +30987,7 @@ paths:
- balances
- flags
examples:
- - &a81
+ - &a83
id: seat_42
name: Seat 42
customer_id: cus_123
@@ -30751,7 +31032,7 @@ paths:
price: null
expires_at: null
invoices: []
- example: *a81
+ example: *a83
x-speakeasy-name-override: create
parameters:
- *a5
@@ -30783,11 +31064,11 @@ paths:
- entity_id
title: GetEntityParams
examples:
- - &a82
+ - &a84
entity_id: seat_42
- customer_id: cus_123
entity_id: seat_42
- example: *a82
+ example: *a84
responses:
"200":
description: OK
@@ -31318,7 +31599,7 @@ paths:
- balances
- flags
examples:
- - &a83
+ - &a85
id: seat_42
name: Seat 42
customer_id: cus_123
@@ -31363,7 +31644,7 @@ paths:
price: null
expires_at: null
invoices: []
- example: *a83
+ example: *a85
x-speakeasy-name-override: get
parameters:
- *a5
@@ -31442,12 +31723,12 @@ paths:
paginated call instead of iterating entities.get.
title: ListEntitiesParams
examples:
- - &a84
+ - &a86
start_cursor: ""
limit: 10
- plans:
- id: pro_plan
- example: *a84
+ example: *a86
responses:
"200":
description: OK
@@ -31999,7 +32280,7 @@ paths:
- list
- next_cursor
examples:
- - &a85
+ - &a87
list:
- id: seat_42
name: Seat 42
@@ -32046,7 +32327,7 @@ paths:
expires_at: null
invoices: []
next_cursor: null
- example: *a85
+ example: *a87
x-speakeasy-name-override: list
parameters:
- *a5
@@ -32216,7 +32497,7 @@ paths:
- entity_id
title: UpdateEntityParams
examples:
- - &a86
+ - &a88
customer_id: cus_123
entity_id: seat_42
billing_controls:
@@ -32225,7 +32506,7 @@ paths:
enabled: true
limit_type: usage_percentage
overage_limit: 120
- example: *a86
+ example: *a88
responses:
"200":
description: OK
@@ -32756,7 +33037,7 @@ paths:
- balances
- flags
examples:
- - &a87
+ - &a89
id: seat_42
name: Seat 42
customer_id: cus_123
@@ -32801,7 +33082,7 @@ paths:
price: null
expires_at: null
invoices: []
- example: *a87
+ example: *a89
x-speakeasy-name-override: update
parameters:
- *a5
@@ -32833,10 +33114,10 @@ paths:
- entity_id
title: DeleteEntityParams
examples:
- - &a88
+ - &a90
customer_id: cus_123
entity_id: seat_42
- example: *a88
+ example: *a90
responses:
"200":
description: OK
@@ -32850,9 +33131,9 @@ paths:
required:
- success
examples:
- - &a89
+ - &a91
success: true
- example: *a89
+ example: *a91
x-speakeasy-name-override: delete
parameters:
- *a5
@@ -32880,10 +33161,10 @@ paths:
- program_id
title: CreateReferralCodeParams
examples:
- - &a90
+ - &a92
customer_id: cus_123
program_id: prog_123
- example: *a90
+ example: *a92
responses:
"200":
description: OK
@@ -32906,11 +33187,11 @@ paths:
- customer_id
- created_at
examples:
- - &a91
+ - &a93
code:
customer_id:
created_at: 123
- example: *a91
+ example: *a93
x-speakeasy-name-override: createCode
parameters:
- *a5
@@ -32938,10 +33219,10 @@ paths:
- customer_id
title: RedeemReferralCodeParams
examples:
- - &a92
+ - &a94
code: REF123
customer_id: cus_456
- example: *a92
+ example: *a94
responses:
"200":
description: OK
@@ -32964,11 +33245,11 @@ paths:
- customer_id
- reward_id
examples:
- - &a93
+ - &a95
id:
customer_id:
reward_id:
- example: *a93
+ example: *a95
x-speakeasy-name-override: redeemCode
parameters:
- *a5
@@ -32986,8 +33267,8 @@ paths:
type: object
title: RewardsListParams
examples:
- - &a94 {}
- example: *a94
+ - &a96 {}
+ example: *a96
responses:
"200":
description: OK
@@ -33201,10 +33482,10 @@ paths:
- coupons
- feature_grants
examples:
- - &a95
+ - &a97
coupons: []
feature_grants: []
- example: *a95
+ example: *a97
x-speakeasy-name-override: list
parameters:
- *a5
@@ -33232,10 +33513,10 @@ paths:
- customer_id
title: RedeemRewardCodeParams
examples:
- - &a96
+ - &a98
code: REWARD10
customer_id: cus_456
- example: *a96
+ example: *a98
responses:
"200":
description: OK
@@ -33266,12 +33547,12 @@ paths:
- reward_id
- entitlements_granted
examples:
- - &a97
+ - &a99
reward_id: reward_789
entitlements_granted:
- feature_id: messages
balance: 100
- example: *a97
+ example: *a99
x-speakeasy-name-override: redeemCode
parameters:
- *a5
@@ -33310,12 +33591,12 @@ paths:
- redirect_url
title: LinkRevenueCatParams
examples:
- - &a98
+ - &a100
organization_slug: acme
env: test
project_name: acme-mobile
redirect_url: https://dashboard.useautumn.com/dev?tab=revenuecat
- example: *a98
+ example: *a100
responses:
"200":
description: OK
@@ -33330,9 +33611,9 @@ paths:
- oauth_url
title: LinkRevenueCatResponse
examples:
- - &a99
+ - &a101
oauth_url: https://api.revenuecat.com/oauth2/authorize?client_id=...&redirect_uri=...&response_type=code&scope=project.read+project.write
- example: *a99
+ example: *a101
x-speakeasy-name-override: linkRevenueCat
parameters:
- *a5
@@ -33372,13 +33653,13 @@ paths:
- env
title: SyncRevenueCatParams
examples:
- - &a100
+ - &a102
organization_slug: acme
env: test
product_ids:
- pro
- premium
- example: *a100
+ example: *a102
responses:
"200":
description: OK
@@ -33444,7 +33725,7 @@ paths:
- results
title: SyncRevenueCatResponse
examples:
- - &a101
+ - &a103
results:
- plan_id: pro
status: synced
@@ -33455,7 +33736,7 @@ paths:
product: created
store_push: skipped
price: set
- example: *a101
+ example: *a103
x-speakeasy-name-override: syncRevenueCat
parameters:
- *a5
@@ -33489,10 +33770,10 @@ paths:
- env
title: GetRevenueCatKeysParams
examples:
- - &a102
+ - &a104
organization_slug: acme
env: test
- example: *a102
+ example: *a104
responses:
"200":
description: OK
@@ -33555,7 +33836,7 @@ paths:
- oauth_access_token
title: GetRevenueCatKeysResponse
examples:
- - &a103
+ - &a105
apps:
- app_id: app1a2b3c4d
app_type: test_store
@@ -33566,7 +33847,7 @@ paths:
environment: production
app_id: app1a2b3c4
oauth_access_token: atk_aBcDeFgHiJkLmNoPqRsTuVwXyZ
- example: *a103
+ example: *a105
x-speakeasy-name-override: getRevenueCatKeys
parameters:
- *a5
@@ -33598,9 +33879,9 @@ paths:
- customer_id
title: MintKeyParams
examples:
- - &a104
+ - &a106
customer_id: cus_123
- example: *a104
+ example: *a106
responses:
"200":
description: OK
@@ -33629,12 +33910,12 @@ paths:
- access_token
- expires_at
examples:
- - &a105
+ - &a107
access_token: am_jwt_eyJhbGciOiJIUzI1NiJ9...
refresh_token: am_jwt_eyJhbGciOiJIUzI1NiJ9...
expires_at: 1781113864000
refresh_expires_at: 1781196664000
- example: *a105
+ example: *a107
x-speakeasy-name-override: mint
parameters:
- *a5
@@ -33658,8 +33939,8 @@ paths:
description: No body. The refresh token is supplied as the Bearer credential;
the response is a freshly rotated access + refresh pair.
examples:
- - &a106 {}
- example: *a106
+ - &a108 {}
+ example: *a108
responses:
"200":
description: OK
@@ -33688,12 +33969,12 @@ paths:
- access_token
- expires_at
examples:
- - &a107
+ - &a109
access_token: am_jwt_eyJhbGciOiJIUzI1NiJ9...
refresh_token: am_jwt_eyJhbGciOiJIUzI1NiJ9...
expires_at: 1781113864000
refresh_expires_at: 1781196664000
- example: *a107
+ example: *a109
x-speakeasy-name-override: refresh
parameters:
- *a5
@@ -33720,9 +34001,9 @@ paths:
- customer_id
title: RevokeKeyParams
examples:
- - &a108
+ - &a110
customer_id: cus_123
- example: *a108
+ example: *a110
responses:
"200":
description: OK
@@ -33736,9 +34017,9 @@ paths:
required:
- revoked
examples:
- - &a109
+ - &a111
revoked: true
- example: *a109
+ example: *a111
x-speakeasy-name-override: revoke
parameters:
- *a5
diff --git a/packages/openapi/openapi.yml b/packages/openapi/openapi.yml
index fd5cb64741..d4ec33f1d7 100644
--- a/packages/openapi/openapi.yml
+++ b/packages/openapi/openapi.yml
@@ -76,7 +76,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -103,10 +102,16 @@ components:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -339,7 +344,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -1995,7 +1999,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -2022,10 +2025,16 @@ components:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -2196,7 +2205,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -2878,7 +2886,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -2905,10 +2912,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -3232,7 +3245,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -4285,7 +4297,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -5141,7 +5152,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -5168,10 +5178,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -5405,7 +5421,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -6813,13 +6828,6 @@ paths:
propertyNames:
type: string
additionalProperties: {}
- version:
- type: integer
- minimum: 1
- maximum: 9007199254740991
- description: Pin the link to a specific version of the license plan. Omitted, an
- existing link keeps its pinned version and a new link
- resolves to the latest.
required:
- license_plan_id
description: Plans offered as assignable licenses under this plan. The full set
@@ -6882,7 +6890,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -6909,10 +6916,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -7872,7 +7885,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -7899,10 +7911,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed
+ top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -8076,7 +8095,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -9184,7 +9202,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -9211,10 +9228,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed
+ top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -9388,7 +9412,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -10475,7 +10498,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -10502,10 +10524,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's
+ consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -10680,7 +10709,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -11016,6 +11044,7 @@ paths:
@param allVersions - Apply the update diff to all versions of this plan. Mutually exclusive with disable_version. (optional)
@param forceVersion - Force versioning even when no customers exist. Mutually exclusive with disable_version. (optional)
@param updateVariantIds - Variant plan IDs to apply this update to. Empty or omitted means no propagation. (optional)
+ @param updateLicenseParents - Parent plan versions that should receive this license-plan update. (optional)
@param variants - Additive variant updates for this base plan. Missing variants are created when name is provided. (optional)
@param isDefault - Whether this is the org's default plan. Cannot be true on a variant. (optional)
@@ -11594,13 +11623,6 @@ paths:
propertyNames:
type: string
additionalProperties: {}
- version:
- type: integer
- minimum: 1
- maximum: 9007199254740991
- description: Pin the link to a specific version of the license plan. Omitted, an
- existing link keeps its pinned version and a new link
- resolves to the latest.
required:
- license_plan_id
description: Plans offered as assignable licenses under this plan. The full set
@@ -11666,7 +11688,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -11693,10 +11714,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -11894,6 +11921,23 @@ paths:
type: string
description: Variant plan IDs to apply this update to. Empty or omitted means no
propagation.
+ update_license_parents:
+ type: array
+ items:
+ type: object
+ properties:
+ plan_id:
+ type: string
+ minLength: 1
+ pattern: ^[a-zA-Z0-9_-]+$
+ version:
+ type: integer
+ minimum: 1
+ maximum: 9007199254740991
+ required:
+ - plan_id
+ - version
+ description: Parent plan versions that should receive this license-plan update.
variants:
type: array
items:
@@ -12249,7 +12293,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -12276,10 +12319,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed
+ top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -13232,7 +13282,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -13259,10 +13308,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed
+ top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -13436,7 +13492,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -15580,7 +15635,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -15607,10 +15661,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -16379,7 +16439,7 @@ paths:
@example
```typescript
// Schedule a transition from a trial plan to a paid plan
- const response = await client.billing.createSchedule({ customerId: "cus_123", phases: [{"startsAt":1784299196316,"plans":[{"planId":"trial_plan"}]},{"startsAt":1785508796316,"plans":[{"planId":"pro_plan"}]}] });
+ const response = await client.billing.createSchedule({ customerId: "cus_123", phases: [{"startsAt":1784744849888,"plans":[{"planId":"trial_plan"}]},{"startsAt":1785954449888,"plans":[{"planId":"pro_plan"}]}] });
```
@param customerId - The ID of the customer to create the schedule for.
@@ -17012,7 +17072,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -17039,10 +17098,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's
+ consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -17713,7 +17779,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -17740,10 +17805,17 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's
+ consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -19465,7 +19537,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -19492,10 +19563,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -22296,7 +22373,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -22323,10 +22399,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -23698,7 +23780,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -23725,10 +23806,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -26047,7 +26134,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -26074,10 +26160,16 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave
+ runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count
+ to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of
@@ -28051,7 +28143,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -28826,7 +28917,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be
purchased.
quantity:
@@ -30560,6 +30650,194 @@ paths:
x-speakeasy-name-override: aggregate
parameters:
- *a1
+ /v1/invoices.insert:
+ post:
+ operationId: insertInvoices
+ description: Inserts or updates up to 500 historical invoices without reading or
+ mutating the billing processor.
+ tags:
+ - invoices
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ invoices:
+ type: array
+ maxItems: 500
+ items:
+ type: object
+ properties:
+ customer_id:
+ type: string
+ description: The customer this invoice belongs to.
+ plan_ids:
+ type: array
+ items:
+ type: string
+ default: []
+ description: Plan IDs represented by this invoice.
+ stripe_id:
+ type: string
+ description: The processor's stable invoice ID.
+ processor_type:
+ enum:
+ - stripe
+ - revenuecat
+ type: string
+ default: stripe
+ description: The billing processor that owns this invoice.
+ status:
+ enum:
+ - draft
+ - open
+ - void
+ - paid
+ - uncollectible
+ type: string
+ description: The invoice status.
+ total:
+ type: number
+ description: The invoice total in major currency units.
+ amount_paid:
+ anyOf:
+ - type: number
+ - type: "null"
+ default: null
+ description: The amount paid in major currency units.
+ refunded_amount:
+ type: number
+ default: 0
+ description: The refunded amount in major currency units.
+ currency:
+ type: string
+ description: The currency code. Defaults to the organization's default currency.
+ created_at:
+ type: number
+ description: The invoice creation timestamp in milliseconds.
+ hosted_invoice_url:
+ anyOf:
+ - type: string
+ - type: "null"
+ default: null
+ description: The hosted invoice URL, when available.
+ required:
+ - customer_id
+ - stripe_id
+ - status
+ - total
+ - created_at
+ description: Invoices to insert or update, in response order.
+ required:
+ - invoices
+ title: InsertInvoicesParams
+ examples:
+ - invoices:
+ - customer_id: cus_123
+ plan_ids:
+ - pro
+ stripe_id: in_legacy_123
+ processor_type: stripe
+ status: paid
+ total: 29.99
+ amount_paid: 29.99
+ refunded_amount: 0
+ currency: usd
+ created_at: 1451606400000
+ hosted_invoice_url: https://billing.example.com/invoices/legacy-123
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ invoices:
+ type: array
+ items:
+ type: object
+ properties:
+ plan_ids:
+ type: array
+ items:
+ type: string
+ description: Array of plan IDs included in this invoice
+ stripe_id:
+ type: string
+ description: The Stripe invoice ID
+ processor_type:
+ enum:
+ - stripe
+ - revenuecat
+ type: string
+ default: stripe
+ description: The billing processor that owns this invoice.
+ status:
+ type: string
+ description: The status of the invoice
+ total:
+ type: number
+ description: The total amount of the invoice
+ currency:
+ type: string
+ description: The currency code for the invoice
+ created_at:
+ type: number
+ description: Timestamp when the invoice was created
+ hosted_invoice_url:
+ anyOf:
+ - type: string
+ - type: "null"
+ description: URL to the Stripe-hosted invoice page
+ id:
+ type: string
+ description: The Autumn invoice ID.
+ customer_id:
+ type: string
+ description: The customer this invoice belongs to.
+ amount_paid:
+ anyOf:
+ - type: number
+ - type: "null"
+ description: The amount paid in major currency units.
+ refunded_amount:
+ type: number
+ description: The refunded amount in major currency units.
+ required:
+ - plan_ids
+ - stripe_id
+ - status
+ - total
+ - currency
+ - created_at
+ - id
+ - customer_id
+ - amount_paid
+ - refunded_amount
+ description: Inserted or updated invoices in request order.
+ required:
+ - invoices
+ examples:
+ - invoices:
+ - id: inv_2b3c4d5e6f7g8h
+ customer_id: cus_123
+ plan_ids:
+ - pro
+ stripe_id: in_legacy_123
+ processor_type: stripe
+ status: paid
+ total: 29.99
+ amount_paid: 29.99
+ refunded_amount: 0
+ currency: usd
+ created_at: 1451606400000
+ hosted_invoice_url: https://billing.example.com/invoices/legacy-123
+ x-speakeasy-name-override: insert
+ parameters:
+ - *a1
/v1/invoices.list:
post:
operationId: listInvoices
diff --git a/packages/openapi/v2.3/contracts/index.ts b/packages/openapi/v2.3/contracts/index.ts
index 8f8df033af..72c3319308 100644
--- a/packages/openapi/v2.3/contracts/index.ts
+++ b/packages/openapi/v2.3/contracts/index.ts
@@ -48,7 +48,10 @@ import {
listFeaturesContract,
updateFeatureContract,
} from "./featuresContract.js";
-import { listInvoicesContract } from "./invoicesContract.js";
+import {
+ insertInvoicesContract,
+ listInvoicesContract,
+} from "./invoicesContract.js";
import {
keysMintContract,
keysRefreshContract,
@@ -132,6 +135,7 @@ export const v2_3ContractRouter = oc.router({
eventsAggregate: eventsAggregateContract,
// Invoices
+ invoicesInsert: insertInvoicesContract,
invoicesList: listInvoicesContract,
// Licenses
diff --git a/packages/openapi/v2.3/contracts/invoicesContract.ts b/packages/openapi/v2.3/contracts/invoicesContract.ts
index 8d6ec86f25..9f6c723ee3 100644
--- a/packages/openapi/v2.3/contracts/invoicesContract.ts
+++ b/packages/openapi/v2.3/contracts/invoicesContract.ts
@@ -1,5 +1,7 @@
import { createCursorPaginatedResponseSchema } from "@api/common/cursorPaginationSchemas.js";
import { ApiListInvoiceV1Schema } from "@api/others/apiInvoice/apiListInvoiceV1.js";
+import { InsertInvoicesParamsSchema } from "@api/others/apiInvoice/insertInvoicesParams.js";
+import { InsertInvoicesResponseSchema } from "@api/others/apiInvoice/insertInvoicesResponse.js";
import { ListInvoicesParamsSchema } from "@api/others/apiInvoice/listInvoicesParams.js";
import { oc } from "@orpc/contract";
@@ -19,6 +21,70 @@ const LIST_INVOICE_EXAMPLE = {
hosted_invoice_url: "https://invoice.stripe.com/i/acct_123/test_456",
};
+export const insertInvoicesContract = oc
+ .route({
+ method: "POST",
+ path: "/v1/invoices.insert",
+ operationId: "insertInvoices",
+ tags: ["invoices"],
+ description:
+ "Inserts or updates up to 500 historical invoices without reading or mutating the billing processor.",
+ spec: (spec) => ({
+ ...spec,
+ "x-speakeasy-name-override": "insert",
+ }),
+ })
+ .input(
+ InsertInvoicesParamsSchema.meta({
+ title: "InsertInvoicesParams",
+ examples: [
+ {
+ invoices: [
+ {
+ customer_id: "cus_123",
+ plan_ids: ["pro"],
+ stripe_id: "in_legacy_123",
+ processor_type: "stripe",
+ status: "paid",
+ total: 29.99,
+ amount_paid: 29.99,
+ refunded_amount: 0,
+ currency: "usd",
+ created_at: 1451606400000,
+ hosted_invoice_url:
+ "https://billing.example.com/invoices/legacy-123",
+ },
+ ],
+ },
+ ],
+ }),
+ )
+ .output(
+ InsertInvoicesResponseSchema.meta({
+ examples: [
+ {
+ invoices: [
+ {
+ id: "inv_2b3c4d5e6f7g8h",
+ customer_id: "cus_123",
+ plan_ids: ["pro"],
+ stripe_id: "in_legacy_123",
+ processor_type: "stripe",
+ status: "paid",
+ total: 29.99,
+ amount_paid: 29.99,
+ refunded_amount: 0,
+ currency: "usd",
+ created_at: 1451606400000,
+ hosted_invoice_url:
+ "https://billing.example.com/invoices/legacy-123",
+ },
+ ],
+ },
+ ],
+ }),
+ );
+
export const listInvoicesContract = oc
.route({
method: "POST",
diff --git a/packages/openapi/v2.3/openapi2.3.ts b/packages/openapi/v2.3/openapi2.3.ts
index 44fc29f411..c0ca7c9ec7 100644
--- a/packages/openapi/v2.3/openapi2.3.ts
+++ b/packages/openapi/v2.3/openapi2.3.ts
@@ -14,10 +14,11 @@ import {
CreateBalanceParamsV0Schema,
CreateCustomerParamsV1Schema,
CustomerDataSchema,
- DfuFlashParamsSchema,
CustomerExpandEnum,
CustomerIdSchema,
+ DfuFlashParamsSchema,
GetCustomerParamsV1Schema,
+ InsertInvoicesParamsSchema,
LATEST_VERSION,
PreviewUpdateSubscriptionResponseSchema,
SetupPaymentParamsV1Schema,
@@ -80,6 +81,7 @@ async function generateOpenApiDocument(): Promise> {
registerInternalSchemas(CustomerDataSchema);
registerInternalSchemas(ApiEventsListV2_3ParamsSchema);
registerInternalSchemas(DfuFlashParamsSchema);
+ registerInternalSchemas(InsertInvoicesParamsSchema);
const openApiDocument = (await generator.generate(v2_3ContractRouter, {
info: {
diff --git a/packages/sdk/.speakeasy/code-samples.overlay.yaml b/packages/sdk/.speakeasy/code-samples.overlay.yaml
index b64d11641c..2ec40beef3 100644
--- a/packages/sdk/.speakeasy/code-samples.overlay.yaml
+++ b/packages/sdk/.speakeasy/code-samples.overlay.yaml
@@ -975,6 +975,42 @@ actions:
console.log(result);
}
+ run();
+ - target: $["paths"]["/v1/invoices.insert"]["post"]
+ update:
+ x-codeSamples:
+ - lang: typescript
+ label: Typescript (SDK)
+ source: |-
+ import { Autumn } from "@useautumn/sdk";
+
+ const autumn = new Autumn({
+ xApiVersion: "2.3.0",
+ secretKey: process.env["AUTUMN_SECRET_KEY"] ?? "",
+ });
+
+ async function run() {
+ const result = await autumn.invoices.insert({
+ invoices: [
+ {
+ customerId: "cus_123",
+ planIds: [
+ "pro",
+ ],
+ stripeId: "in_legacy_123",
+ status: "paid",
+ total: 29.99,
+ amountPaid: 29.99,
+ currency: "usd",
+ createdAt: 1451606400000,
+ hostedInvoiceUrl: "https://billing.example.com/invoices/legacy-123",
+ },
+ ],
+ });
+
+ console.log(result);
+ }
+
run();
- target: $["paths"]["/v1/invoices.list"]["post"]
update:
diff --git a/packages/sdk/.speakeasy/gen.lock b/packages/sdk/.speakeasy/gen.lock
index f74d993715..34f153646c 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: a70ac88ba6c84057133a7a3fb23e9617
+ docChecksum: 0ec6edf850540a933f6a79edc67f051b
docVersion: 2.3.0
speakeasyVersion: 1.762.0
generationVersion: 2.882.0
releaseVersion: 0.10.18
configChecksum: 751d545c3d03661781a8ccf68e83a1b6
persistentEdits:
- generation_id: 94bd460b-2068-4965-96e5-88c55dc68401
- pristine_commit_hash: 82e66ed6630907ba7231f410450571f0f187793b
- pristine_tree_hash: 38d22d687bc8b0b93baed9f23be07ece27474e14
+ generation_id: 0d6ea8f3-6cb4-4f5b-96b0-c7a3cddcdbce
+ pristine_commit_hash: 6e27cace85dacb2a8c4f37e6f8a89151e63daef3
+ pristine_tree_hash: bd9de794c5b0304d44e2a94d235508aa94aacee7
features:
typescript:
additionalDependencies: 0.1.0
@@ -182,8 +182,8 @@ trackedFiles:
pristine_git_object: 92d058f3586e4370fe64fe61ab605c68df98539b
docs/models/attach-auto-topup.md:
id: f199077483db
- last_write_checksum: sha1:10edc41e0abf9ca2554a1c120eb6898e30316061
- pristine_git_object: 030383b8a163fa08385b084c5f4ec20b05f71665
+ last_write_checksum: sha1:fbc53a41a4e322a795aa4d153fb974c082e4c28e
+ pristine_git_object: 539db8c0564f63fa0cb456425379543418fb4c49
docs/models/attach-base-price.md:
id: f0987a200c0d
last_write_checksum: sha1:0595dc42e9a153095daa7695ba01dd3f3335bf0a
@@ -390,8 +390,8 @@ trackedFiles:
pristine_git_object: 7a4cd9d8a1f0695b36c5fa314fda9de65365867e
docs/models/attach-purchase-limit.md:
id: af964d969c78
- last_write_checksum: sha1:2748c1abe894e046775c958fdf77cf44e736ec72
- pristine_git_object: 020e9a9a2c8009c372a2edd1f17af89606abefe0
+ last_write_checksum: sha1:4341682d96cab477cc441ab297fdfb8d7cc89f85
+ pristine_git_object: 28f19435079e671d0d35c31d5bf859a1dd8c5df8
docs/models/attach-redirect-mode.md:
id: e3c87b6f489b
last_write_checksum: sha1:822e38dbdafa354554ba2c73bededa06ec97c9a0
@@ -686,8 +686,8 @@ trackedFiles:
pristine_git_object: 141e593599c2b1ba12c7149749a89d119e50c78a
docs/models/billing-update-auto-topup.md:
id: e8837f60d4f6
- last_write_checksum: sha1:d9bb81d15d5b56a6aa5530ed0618d4c227ae204e
- pristine_git_object: 234dc11db069fe4fd0fa130bcf1dd0311caff2bb
+ last_write_checksum: sha1:0c81a621a14bd2358dd6c96b30f01ff7f4c8c3c9
+ pristine_git_object: 5eea03ca83c09455b83e5b9821b89a1802fe34f3
docs/models/billing-update-base-price.md:
id: 7a3d2f48b101
last_write_checksum: sha1:08dc49dbe42f6f1f8c7c6466d0a5fc38690ae1a6
@@ -866,8 +866,8 @@ trackedFiles:
pristine_git_object: d7988287f09875b437308cc772473b23a1f64683
docs/models/billing-update-purchase-limit.md:
id: 9b70580f4662
- last_write_checksum: sha1:9ffc9f99ab3e11294a5e0b42a7e3ada78c5668fc
- pristine_git_object: 9fa0f09774d8f2b3646422500a15d9686e9077f4
+ last_write_checksum: sha1:61bf8866a9a4aa69fee017fff8944a0769bcfb1d
+ pristine_git_object: 5ce193bc80b822b69d0484b8a4828f3d7c9cdd57
docs/models/billing-update-recalculate-balances.md:
id: 8ead4615c410
last_write_checksum: sha1:faeed44cbca8f10ff5e55213ce959bba7da80fa5
@@ -1530,8 +1530,8 @@ trackedFiles:
pristine_git_object: 11bf4a9d74e75a895e27489a55444fbe76d6488e
docs/models/create-plan-auto-topup-request.md:
id: 4220992b4af0
- last_write_checksum: sha1:58cd30fcf32cfeaf91057c9707e7f2a77c03268c
- pristine_git_object: 54c7c845aa40965f29103a02c38fe1ce5d9c523a
+ last_write_checksum: sha1:15ea0b67158757f90aa76375cba09b18d10d609d
+ pristine_git_object: 94931759aecac4b2cf105fab14a19def4ab24eb2
docs/models/create-plan-auto-topup-response.md:
id: 729f2f97759f
last_write_checksum: sha1:acd8933ae36be68ed16aecfc56b152aac2c41ee9
@@ -1826,8 +1826,8 @@ trackedFiles:
pristine_git_object: c4450261966af77f07f67824851ad2817e23ddde
docs/models/create-plan-license.md:
id: 3c6746410f2b
- last_write_checksum: sha1:5d61d6a10e46fd0aacc2e17aea549134f324cb89
- pristine_git_object: 8f44d671f88b05b49687eac7c58052bc196cc8e8
+ last_write_checksum: sha1:8d39c4cba301df2a2501b729b6a01d63724d5cd8
+ pristine_git_object: 418e97658c18dd6351558cca9a1b8860e029a3dd
docs/models/create-plan-limit-type-request-body.md:
id: 2901a51053ea
last_write_checksum: sha1:f16009db02d445e03c947fe39195ac489d6b5cd3
@@ -1914,8 +1914,8 @@ trackedFiles:
pristine_git_object: ca1607781c10074ca0784aace3890e39717f358f
docs/models/create-plan-purchase-limit-request.md:
id: 89826591fd3e
- last_write_checksum: sha1:e9f3a0ade7e33a18e5bb449f9a61e65abb85b403
- pristine_git_object: b6cc304a9fdfc89387beea6d0b6260ee1ce85317
+ last_write_checksum: sha1:d14b15bb2754d227574f4231fda11e7e58032c43
+ pristine_git_object: 08d1f7580b255d0b1d3c16e7b530ea3ad46a6af1
docs/models/create-plan-purchase-limit-response.md:
id: 3f6d5600a425
last_write_checksum: sha1:5acb9b88e680b4e63afcf41eb9e87c56fef2eb38
@@ -1994,8 +1994,8 @@ trackedFiles:
pristine_git_object: 3d56ab7c38e3f8bcdc2bcf381759c5d2e64e3401
docs/models/create-plan-variant-details-auto-topup.md:
id: 47498d61f061
- last_write_checksum: sha1:5d733417db67c0c98c73c136b5443303397be8e3
- pristine_git_object: 25bc6d98737056b6975e2903a70fb960f6d12efb
+ last_write_checksum: sha1:db0bef949115236f0bc93d764eca609a0c8971f3
+ pristine_git_object: a3645b9f82b290d0e2d6ce8460b5d0323e4a282c
docs/models/create-plan-variant-details-billing-controls.md:
id: da06df4a7ee6
last_write_checksum: sha1:805b0a46abbe809aa3bb58ca4d7e6f73aec45aa2
@@ -2046,8 +2046,8 @@ trackedFiles:
pristine_git_object: b9d0c70eb15bea1107b9b0506452320526d16d9a
docs/models/create-plan-variant-details-purchase-limit.md:
id: ce32aa245fd1
- last_write_checksum: sha1:ce0ddd47a2b6fb219a6c90cde7261f62403c361d
- pristine_git_object: a5c02353db718ddf182533ae18ef42651a5d17b7
+ last_write_checksum: sha1:48f6098df0c72e426cd5d2d06f2bd8220defcac8
+ pristine_git_object: 508d35dc99f7338479e5fc4642766959254c7ce0
docs/models/create-plan-variant-details-remove-item-billing-method.md:
id: 2f9aabcb612f
last_write_checksum: sha1:2be8fabed97356001281f1a8e52e677b099785ca
@@ -2182,8 +2182,8 @@ trackedFiles:
pristine_git_object: 1509adb1d6cae175888e8052ce9b3e90ab756c96
docs/models/create-schedule-auto-topup2.md:
id: e67a209bf731
- last_write_checksum: sha1:8a6ca1c592f983b741f2e92d4df5c89afeee9904
- pristine_git_object: 063876dcb27b7737591e29d389f88214c8bc9862
+ last_write_checksum: sha1:247111e9a86f895628e790595722fcb208dce4f8
+ pristine_git_object: e86520ff22807d6cb3beb225f21e76d9821a78fd
docs/models/create-schedule-base-price2.md:
id: 56793b9353fd
last_write_checksum: sha1:c345ab2b689f3c3460f361c8bfef6417530abecf
@@ -2326,8 +2326,8 @@ trackedFiles:
pristine_git_object: aee3872ba68699ba1279fdb7ceef92be1f839377
docs/models/create-schedule-purchase-limit2.md:
id: 1216e233fbe7
- last_write_checksum: sha1:51e8d257d0dbbc34a087e987a5e72b4a70fbf44e
- pristine_git_object: 9f7c2bc413d5b5aa11d1a07d39d004f45f43b111
+ last_write_checksum: sha1:ce16339c8c5fa2610a4280bcea2b019200b0477a
+ pristine_git_object: 71e6710bf51218dd086ee0d6d02bbe97af6898d6
docs/models/create-schedule-redirect-mode.md:
id: 4414e1893b11
last_write_checksum: sha1:a370027bd5e56f88c2223275e6131bfafacc6e44
@@ -2386,8 +2386,8 @@ trackedFiles:
pristine_git_object: 240e81faccbe40b3977164f2a1df81eefa704cc5
docs/models/customer-data-auto-topup.md:
id: a9028229550e
- last_write_checksum: sha1:97f734c3650a194091ce892a213014c746b124ed
- pristine_git_object: 656aaaab7f348684ec056ef2ef997dd1bf6f7b20
+ last_write_checksum: sha1:462bd44b03e841f80699f9227557096123166a6f
+ pristine_git_object: c23728ad2717cf985aada4d24235993dc2f098aa
docs/models/customer-data-billing-controls.md:
id: ca09ea9a9d79
last_write_checksum: sha1:c5e4f1df7c531adf0d73ee3b777f17524f49623b
@@ -2414,8 +2414,8 @@ trackedFiles:
pristine_git_object: ddbb71ee6a29627e29d0d92640299bb671618c11
docs/models/customer-data-purchase-limit.md:
id: 52a86c6092c4
- last_write_checksum: sha1:869703db26a5e1645575ed9992cfe8146c0925d7
- pristine_git_object: 4e7adc776f479f261795c577d468142a330355e9
+ last_write_checksum: sha1:fae709851148f62f267f7dd06255675021c4ebe1
+ pristine_git_object: 7e8df701ff9c297a16ad3dce4fcc0cf50d058f40
docs/models/customer-data-spend-limit.md:
id: d6adb304f72d
last_write_checksum: sha1:9732fa8bdcb1943408ed95c28b41fea452f22ab1
@@ -3118,8 +3118,8 @@ trackedFiles:
pristine_git_object: 8f240bde6709a476ec744ba6216e1f0af24e113f
docs/models/get-or-create-customer-auto-topup.md:
id: "673761036094"
- last_write_checksum: sha1:82829d5ba5755f620fd2e571c254d3ef6eadf82e
- pristine_git_object: 8531f2bdc27ac9891b9bd369865f4cdbee359d73
+ last_write_checksum: sha1:ecdd5b284bfbedb11b470d382497a8bb209d4237
+ pristine_git_object: c31169ac6fb4b34d9e846684a0ff6c04d8d2cc6c
docs/models/get-or-create-customer-billing-controls.md:
id: e7ee10d9a50b
last_write_checksum: sha1:ad80588d690231ca5f18a65682b1f1f2cbada54c
@@ -3158,8 +3158,8 @@ trackedFiles:
pristine_git_object: 95783157c590556a4a603f88b326ec681d72d338
docs/models/get-or-create-customer-purchase-limit.md:
id: 409c503aeb70
- last_write_checksum: sha1:e3687c4701b8de15825ca18f6c2cbb9b53e5d8b4
- pristine_git_object: 7e2acbb5609dcc6998778dfcb03ab41fd0106700
+ last_write_checksum: sha1:078ed99a39fbb1b5c47e40890eab2c6b5dbe0f2c
+ pristine_git_object: 1f3aea58b35d99647fa2891de13b62e27c62be62
docs/models/get-or-create-customer-spend-limit.md:
id: 6bc905d17111
last_write_checksum: sha1:c618c77388ffe370ac6d0ff6a51eb28bb09be0d4
@@ -3430,8 +3430,8 @@ trackedFiles:
pristine_git_object: 54129a2c2a0703561c4ab35f8e60ab2252994e87
docs/models/get-plan-variant-details-auto-topup.md:
id: 96512e86d732
- last_write_checksum: sha1:089f2a2051bc43467294798229e53591429f1ffd
- pristine_git_object: dcd2bc82519a674511ac31ba8430316e8dc5b520
+ last_write_checksum: sha1:2f7908950021cbc569a30040d9bf233cb89fd537
+ pristine_git_object: b83e1657a50e5d02c9ff8fc008cff330a75619db
docs/models/get-plan-variant-details-billing-controls.md:
id: d43998bdec25
last_write_checksum: sha1:3ad5bbbfcea35a7b55531167d9b443ef64c048b8
@@ -3470,8 +3470,8 @@ trackedFiles:
pristine_git_object: 65ade8c40c69d89ed83c6d86e036418ef876f7f8
docs/models/get-plan-variant-details-purchase-limit.md:
id: fa2a6849af3e
- last_write_checksum: sha1:2308fd5570f9e1f105c4e9b182b2ee00d700c912
- pristine_git_object: dc18a58e553f0d0dfcd1682d6fc19a830dcad02a
+ last_write_checksum: sha1:05a769856eb8b6ef5b840ed0e45a6e92a2f6e4b0
+ pristine_git_object: 5737c24d68c4c80403f06ebcba92b7ec2fddf5f6
docs/models/get-plan-variant-details-reset-interval.md:
id: fd6c1816e3c3
last_write_checksum: sha1:49bff5ee3f007b97342f3fda25f68934badeff6e
@@ -3600,6 +3600,34 @@ trackedFiles:
id: d32fd48d0d1e
last_write_checksum: sha1:1db7b73b664823c5938063172e43017b807eea84
pristine_git_object: c3892aa1ab8837c731f49e042711f10729d82df9
+ docs/models/insert-invoices-globals.md:
+ id: e4793d06ea74
+ last_write_checksum: sha1:c12f39ffe8f3577f1de4aa29ba78cee7eaeb6ca3
+ pristine_git_object: 8be226f893bae107bd0010e2d6a89ae5384b0142
+ docs/models/insert-invoices-invoice-response.md:
+ id: c72ba5ab432a
+ last_write_checksum: sha1:5533605aca672b470ab9c8275d12e3ce8380a635
+ pristine_git_object: 6cae687c4df31a523a0744e6bd1a0a2acd32ffa0
+ docs/models/insert-invoices-params.md:
+ id: 9312155ed5e1
+ last_write_checksum: sha1:4e42196c3854b6bdaea7338e32f3abd9f07c04cd
+ pristine_git_object: d4ffadc60dfbf3be734090c1fd4bf678795b5398
+ docs/models/insert-invoices-processor-type-request.md:
+ id: 59401b3a55ad
+ last_write_checksum: sha1:4591008c83eb0cd22818a05e6376fb325ad1f47c
+ pristine_git_object: 294d576f892a1dbac853307873ca66133ccdee68
+ docs/models/insert-invoices-processor-type-response.md:
+ id: f62c7952ce8c
+ last_write_checksum: sha1:9899c179309466b2feac5c7c85bb11f874461280
+ pristine_git_object: 5284be33c06bcfe75504e88a9ca7d970e92c0462
+ docs/models/insert-invoices-response.md:
+ id: b3b879528460
+ last_write_checksum: sha1:eec651926ff7e77f662dab455b916e02d2e50781
+ pristine_git_object: b297e7e131b9e5182286416154a56968ddeae714
+ docs/models/insert-invoices-status.md:
+ id: 5b172c93fb62
+ last_write_checksum: sha1:03424c06abb1e77428c3f4a915c9c6a1fe589ab8
+ pristine_git_object: 901151bdb3172d0427221d3d61f249f5ad666939
docs/models/intent.md:
id: a4a6fa8818f7
last_write_checksum: sha1:39fa3a78bda572fa9b8a765966413b6d2b4436ea
@@ -3612,6 +3640,10 @@ trackedFiles:
id: ab31c20717b4
last_write_checksum: sha1:352ae4842b17c4e788dcab9ec3b739f84eed343f
pristine_git_object: edb8c4e072206f2fbb44ed7435ffc97071b3c459
+ docs/models/invoice-request-body.md:
+ id: d00e1fe176ed
+ last_write_checksum: sha1:738ee16658b9443ecc501dce96d67e8f033a3c03
+ pristine_git_object: b40b5a52efb8af122d7fb792a1d9d14fe48e5614
docs/models/invoice.md:
id: 18e2034f11ad
last_write_checksum: sha1:7876f7e8c989534234e6b9dac8900a0e3d8952b5
@@ -4062,8 +4094,12 @@ trackedFiles:
pristine_git_object: f7450741166428bc97e079c1e45588e9e488e243
docs/models/list-invoices-params.md:
id: 9fbe6a2d604b
- last_write_checksum: sha1:7ca491c3a0c8a4000b618c2c31ffa837214973bf
- pristine_git_object: 873ed0f22aad154ce2b8191d4b18eddc548b8cff
+ last_write_checksum: sha1:f1df1165a6e03f5a2ebb77a2b10c52053931a88d
+ pristine_git_object: 5c2b2d060fba9db0f6e36c4db003b668b7cd6d25
+ docs/models/list-invoices-processor-type-request.md:
+ id: 37ffbe3a119b
+ last_write_checksum: sha1:8db44e999028b94eb770b3fc76bc6f28746e0168
+ pristine_git_object: 42d29ddb292b5b4460b5648a5364fe47c81917e0
docs/models/list-invoices-response.md:
id: e981f0d9a6d3
last_write_checksum: sha1:da613f8a7d3dd645acc3e88dc007553a3349d7aa
@@ -4326,8 +4362,8 @@ trackedFiles:
pristine_git_object: 37f977d5325587be84a9196fad8a8b24422ccfc9
docs/models/list-plans-variant-details-auto-topup.md:
id: 512e7d204755
- last_write_checksum: sha1:8c5b13e07a18f9ac18771394276561a3a74612ad
- pristine_git_object: 95e55b8bd56f7690c8e6e105ea456d458abe6c8c
+ last_write_checksum: sha1:cebd75f0b0c6c7e8673baf9fe337b871a484887e
+ pristine_git_object: 144a60dcad2b909d6e372d2bc4e75bf874a25a40
docs/models/list-plans-variant-details-billing-controls.md:
id: b01a238f2194
last_write_checksum: sha1:64620dd73d06f9d217ad0a223f0e31d7b07e0f92
@@ -4366,8 +4402,8 @@ trackedFiles:
pristine_git_object: 9c8c5bed58c747077ae5cf8bd7a3a5522ef2b53d
docs/models/list-plans-variant-details-purchase-limit.md:
id: d834c9c2d7d1
- last_write_checksum: sha1:5d1b4f429f2fb17f519aa905fbb7f382a35f94f4
- pristine_git_object: dc942c9cd5f21bccc76a7912ca2ea91abc268bbc
+ last_write_checksum: sha1:ce8030c66c248627db6ac1cfb25f948349522f86
+ pristine_git_object: f04ae05ae2841895044de6c17f0befcc69c93d3b
docs/models/list-plans-variant-details-reset-interval.md:
id: e6025a763aa3
last_write_checksum: sha1:ab1a97e311be254067fff8705cf18ec034d01851
@@ -4870,8 +4906,8 @@ trackedFiles:
pristine_git_object: 91e2e12508c0f73d9ec8cc8e1f292281d21dd35d
docs/models/plan-variant-details-auto-topup.md:
id: 9b9cc6c8a012
- last_write_checksum: sha1:15f55a24c55a1b22cc8dc38799f45d9af36f0a64
- pristine_git_object: 331e4f9a3e023f287fc4e160d2744e4ecdab2744
+ last_write_checksum: sha1:62bb552ec3c11666b22dfed9162505ab23435f3b
+ pristine_git_object: 6a41d1cc88a0c85923e2ed230d28374f987f1e10
docs/models/plan-variant-details-billing-controls.md:
id: 0639706e276a
last_write_checksum: sha1:ba4a907b80ee905296aa61db3185657d74fffa48
@@ -4902,8 +4938,8 @@ trackedFiles:
pristine_git_object: e19fcc8797756448993708965cac3506a24b944c
docs/models/plan-variant-details-purchase-limit.md:
id: 18c5345423f1
- last_write_checksum: sha1:fbe31130ab79cfb6376b9b51db6b8adbe44d3fdf
- pristine_git_object: 1876f167bcf926588693fb848c54e47dd258202a
+ last_write_checksum: sha1:6702981c6a15db0d78ebf13a847c6b26bde28d9a
+ pristine_git_object: 7bd6830e5a56cac53d706cd7d42480360075ac5b
docs/models/plan-variant-details-reset-interval.md:
id: d92854106bdd
last_write_checksum: sha1:a4a371045792bb04378dbecbbc468f262320936d
@@ -5026,8 +5062,8 @@ trackedFiles:
pristine_git_object: 233439ea9e7483337c1974e355294de7350d5b24
docs/models/preview-attach-auto-topup.md:
id: 5c1a9152aa0f
- last_write_checksum: sha1:fd363a77a28d2fedf5f1a899f061197cc2361826
- pristine_git_object: f11fa75c5a1f0715c8c4b3699042f14a47e633f8
+ last_write_checksum: sha1:68eadc96d212d46d7e0730016537f33b86c1106b
+ pristine_git_object: 7dec4bd69311d6154c4d689b07960f7965226a37
docs/models/preview-attach-base-price.md:
id: c03a523d8cd0
last_write_checksum: sha1:431c21f19b6c6a8b14d349f53935611ab3324a01
@@ -5262,8 +5298,8 @@ trackedFiles:
pristine_git_object: 6a1f268957b8dec1015680d5828dcfbc11429c4f
docs/models/preview-attach-purchase-limit.md:
id: d03ba8e9c95f
- last_write_checksum: sha1:84100d7ea5105cb94f5756e2bd3dffcd2936e649
- pristine_git_object: 75b68d76f0bd1911e841ba1d905030276c7b7d09
+ last_write_checksum: sha1:455252af9abd470578a74a9115fd43d06bbe1deb
+ pristine_git_object: 74c463a4c17c4ad61e6b6c2290cf70d9e7a30077
docs/models/preview-attach-redirect-mode.md:
id: 09a67bc1b945
last_write_checksum: sha1:3af045689583aa733d59cda58dbe2f7dbb52ad2c
@@ -5782,8 +5818,8 @@ trackedFiles:
pristine_git_object: 2dfbb6d2767db7a27132c846b17b7dd83d30f668
docs/models/preview-update-auto-topup.md:
id: ce3863fb653f
- last_write_checksum: sha1:2b74bcaa771787388deee5c1e887a96428f826e7
- pristine_git_object: 3c12069fe7f79104540059d869d578e9fdfd166e
+ last_write_checksum: sha1:1a5821f0aa87bb8e89444144ed6f46184cab864f
+ pristine_git_object: 1fa274d7a1cc4cee500beb6ef20372c3d17d1d83
docs/models/preview-update-base-price.md:
id: 2da5e78ec0e0
last_write_checksum: sha1:897365ce797986b84c8fc197e5173764ed8a7f8e
@@ -6006,8 +6042,8 @@ trackedFiles:
pristine_git_object: 44581c88aa8b9c135e1e9457d0095849de0e04e0
docs/models/preview-update-purchase-limit.md:
id: 2425fb2db9d0
- last_write_checksum: sha1:ddf8b4e6dde28d414e952a380759dc9afa0e8d8c
- pristine_git_object: f0c578ec22a4735516d432ace00e9769033cf0fa
+ last_write_checksum: sha1:824abd986be1d08bf257442efd3c291e15a1a22c
+ pristine_git_object: 731515178301229f3e7a9966599c0317693e9244
docs/models/preview-update-recalculate-balances.md:
id: 8492e617571a
last_write_checksum: sha1:b42eaaf40821a806f22d0a5be11a543d1ef12ce2
@@ -6160,10 +6196,6 @@ trackedFiles:
id: 284eb48beca2
last_write_checksum: sha1:a7aff4d46925fac8f887760f5438bc72d9d9d1d5
pristine_git_object: a5910b07c02a166869181f5fd30a3192490c98dc
- docs/models/processor-type-request.md:
- id: 30645e14d67c
- last_write_checksum: sha1:03eec6aa4d19c60ba0493f9ec76eed35b4f38761
- pristine_git_object: 4d6e5d310139fa843e271d7af0a83ac9a874746d
docs/models/processor-type.md:
id: 4e1ee9632454
last_write_checksum: sha1:e5c091efb3ac5d256119b589918fb9e3fd91b92f
@@ -6390,8 +6422,8 @@ trackedFiles:
pristine_git_object: 48d618e57811e63de01d8ee23b8926f7a6fd2b6a
docs/models/setup-payment-auto-topup.md:
id: 2439c659bd09
- last_write_checksum: sha1:56c371a48fe179270b5bb9f45538e32b8b7ff131
- pristine_git_object: 6ad9b3ec57408c2030e4e40938a004901fbab9f3
+ last_write_checksum: sha1:50e815c0b5cad1afe068a412cc0c7d5c11dfe0d2
+ pristine_git_object: 356f7afeddecdff8fc4de889f33df8f2b8c84696
docs/models/setup-payment-base-price.md:
id: 7379e4922abd
last_write_checksum: sha1:8bd14fbd0c7058d285aed7feb8105c672c071bf6
@@ -6566,8 +6598,8 @@ trackedFiles:
pristine_git_object: 5e4238dc6d5861f0b1a387d29faf4a8ee4d0663b
docs/models/setup-payment-purchase-limit.md:
id: dbdefd4815a2
- last_write_checksum: sha1:636f2c215ba1dfb78ab8171d1a239956209d445f
- pristine_git_object: 4e7e019e30b801b724068a6c4fcc04bb77a2ecee
+ last_write_checksum: sha1:5e226f710544fa34f1c74c68dd5f876f819f80ab
+ pristine_git_object: e0b4f75877f6eb45512c9c5f0bf5293849677512
docs/models/setup-payment-remove-item-billing-method.md:
id: 71080c21cd33
last_write_checksum: sha1:784cbf9747dc8299617e342b91a9b75a392b4bd3
@@ -6882,8 +6914,8 @@ trackedFiles:
pristine_git_object: 499e9e62b93f93e4774017a911c621b91407c3bb
docs/models/update-customer-auto-topup-request.md:
id: 9d975c2052ed
- last_write_checksum: sha1:9c480fc958172d19f79d23c06eab743d9fadef0a
- pristine_git_object: af2107e24d41ddaf7ee5d3b349fdf2fa84bbd5ce
+ last_write_checksum: sha1:7ce7ab5be0d39069778248aac2fa1e2b3d05c8e8
+ pristine_git_object: cbd2e82202476708c61b3a97d1c81b632f2c77c3
docs/models/update-customer-auto-topup-response.md:
id: 3c24a7d246cc
last_write_checksum: sha1:335579c7f116e577885bb990a0fb822f23e36c19
@@ -6998,8 +7030,8 @@ trackedFiles:
pristine_git_object: d8da2e158e5dd58bbce8592b7129ed52f11269d9
docs/models/update-customer-purchase-limit-request.md:
id: ae71fddbf2a5
- last_write_checksum: sha1:8ad56d2a18ce89fd91461ffa51cf3aab0e3872c2
- pristine_git_object: 94023a7f19ac6c8e9bb228f623eee1ff95dd41d8
+ last_write_checksum: sha1:190149ab65bade4cc03c17f7c2e6055c7fab8d3b
+ pristine_git_object: c712b48298e6f65ef95366d16c6b8f80efb327a1
docs/models/update-customer-purchase-limit-response1.md:
id: 4706d17e1852
last_write_checksum: sha1:1a6e38a611106a213c9658d5461ba3f3c20d0d8b
@@ -7320,6 +7352,10 @@ trackedFiles:
id: 55ef8f61ed28
last_write_checksum: sha1:faf37d8d85056c22c1e7718c15ebce9706291ff5
pristine_git_object: 46197bb53415db585d6e501b766ff57c2affbf9f
+ docs/models/update-license-parent.md:
+ id: eb2ed547ea94
+ last_write_checksum: sha1:f60a9fc1095516e85c356b12b7836c327859a64c
+ pristine_git_object: 0f86a4230aa8c556c15ec13e2a3eac1e3e55f8f7
docs/models/update-plan-additional-currency-request-body.md:
id: bf2db8c4a85c
last_write_checksum: sha1:c6607ea607db690c5e38270e22bcf994d6e17649
@@ -7334,8 +7370,8 @@ trackedFiles:
pristine_git_object: e67655ce96d6c0b48a0a6930027a0394b816136d
docs/models/update-plan-auto-topup-request.md:
id: b29b46295c91
- last_write_checksum: sha1:0d3af0f345fffee42039e8f46512857dfb947e47
- pristine_git_object: 1e2dfa302a60d3f00c49ea7ab9e37726837a0300
+ last_write_checksum: sha1:1aaf1abfae890a56b00e2d692422a43e2059de8b
+ pristine_git_object: 1e4060547b23bbf35b8d55b634fc77c381c59706
docs/models/update-plan-auto-topup-response.md:
id: ff840e2867e3
last_write_checksum: sha1:33ad47af3f462bd8896bbb461e4be0e751c721f4
@@ -7638,8 +7674,8 @@ trackedFiles:
pristine_git_object: e920c1ad84dce2c76a46dc349a9d9c0e66f32895
docs/models/update-plan-license.md:
id: 84c810207333
- last_write_checksum: sha1:cf11ed775564e615747669956b5a3f1f124599ff
- pristine_git_object: 767ecd125c52a841804d6491e56fd4eaad002dab
+ last_write_checksum: sha1:e09274ea745f33a941075af6e0bb36e4c5b16dc1
+ pristine_git_object: 1a57ae64a161f3ad40845bf3e33f4b7f39fe091d
docs/models/update-plan-limit-type-request-body.md:
id: 2ad33ffd7761
last_write_checksum: sha1:a3878d58295eabb4e8d503a63ba3358351896047
@@ -7666,8 +7702,8 @@ trackedFiles:
pristine_git_object: 637649d78c9e068c2842d5ca63e7d1428dcf8ab3
docs/models/update-plan-params.md:
id: baf87a97d876
- last_write_checksum: sha1:cfdef069ed201df7926e7e198a60120104bb823c
- pristine_git_object: 3b764129827dce8c36aec412a87bf4c19f71c02a
+ last_write_checksum: sha1:28f6824596dcec30aaf7ebfb80475f54e3d5d1af
+ pristine_git_object: cb6e116b5030ccefeb1c70c9aff38bd9a29494e5
docs/models/update-plan-plan-item-filter-response.md:
id: df364ea1debe
last_write_checksum: sha1:ed862f3e60fc738b64882e636f1a56c3c2686e99
@@ -7722,8 +7758,8 @@ trackedFiles:
pristine_git_object: af094163ac68f309ea6f201e9b73c8e97259d375
docs/models/update-plan-purchase-limit-request.md:
id: ba80a09b0b07
- last_write_checksum: sha1:63deae9440dec30b9e2c3ba6b5369c7a12c501c5
- pristine_git_object: 6232ea7446b1133e621cd61c6de147b97406270b
+ last_write_checksum: sha1:bd78d3837195e4870093e7031e65c2c6369c4907
+ pristine_git_object: 544444c28b5f53ec32e7a637dcda40b4ecda2501
docs/models/update-plan-purchase-limit-response.md:
id: b6e47d6bb20d
last_write_checksum: sha1:cf7983205ce16ca91c186d7506f51e8bc7b51f40
@@ -7802,8 +7838,8 @@ trackedFiles:
pristine_git_object: cd8e4d700c0809f7f4152d9e01370fe121e13072
docs/models/update-plan-variant-details-auto-topup.md:
id: 3a5cb6662734
- last_write_checksum: sha1:cb74e759841471fab95c9fa3e4aa506589d634d6
- pristine_git_object: fdd3620d4b65bcecb178442b0dae36d38ef2c88d
+ last_write_checksum: sha1:c525e5ca3e1bbda34152af28e410938274f410e5
+ pristine_git_object: 1fd5250157fd690031821a6b925ab03655ef5d54
docs/models/update-plan-variant-details-billing-controls.md:
id: 6bd23551de5b
last_write_checksum: sha1:0c1084e33f9e783d346f3fa72281e5ba5ed49cb2
@@ -7854,8 +7890,8 @@ trackedFiles:
pristine_git_object: a366e8f356f83e8bc2614e3af5234f5bc41bd920
docs/models/update-plan-variant-details-purchase-limit.md:
id: 5c81ee49b05f
- last_write_checksum: sha1:451b8a6a1f4d2a666120f8c81d1a42977837bba5
- pristine_git_object: 1711bb5992e3635420d7e0b6d25c577ae781f824
+ last_write_checksum: sha1:98e0e146548ef6f507e23a0e9219feaf7f11e6cf
+ pristine_git_object: 8430b42186b812cfb297f9b3ec2d529e16a349d2
docs/models/update-plan-variant-details-remove-item-billing-method.md:
id: f4ef80c199d0
last_write_checksum: sha1:3d32672d4d9b687293d428b2c26526158e129521
@@ -7950,8 +7986,8 @@ trackedFiles:
pristine_git_object: 5c01e5c61c113c0c64da177542b6c92b2eec5063
docs/models/variant-auto-topup.md:
id: dd1ea9ccb7ca
- last_write_checksum: sha1:0a9ae7dfb6a4c6f9f024d531a713c74fe0f8931e
- pristine_git_object: 588a9c80ed3148d23c18f3d1f0ceba662d5f85e0
+ last_write_checksum: sha1:cb4f58b70bdd86c098bf7c6836850335c6dc3af8
+ pristine_git_object: d82955c68f3f6b2a11298e32a4f2393153d2946d
docs/models/variant-base-price.md:
id: dd39d4ab9863
last_write_checksum: sha1:4643589c5d598d1c6ae1f58a8ef4e96c41e3df28
@@ -8054,8 +8090,8 @@ trackedFiles:
pristine_git_object: dab76692c62d7e6c36714388a117c3390a2efa0e
docs/models/variant-purchase-limit.md:
id: 10ee4817654f
- last_write_checksum: sha1:664c1e99e70ab61e1260e870f7782ae1afe3ab8d
- pristine_git_object: 4688cdcf3491d19a7607538a7b9e88c27f8220c9
+ last_write_checksum: sha1:a7aaa37a255059a0c83ce6630dde006b9d06b735
+ pristine_git_object: 8dee260dff48c7b586ac1dc8b07400322dca3d68
docs/models/variant-remove-item-billing-method.md:
id: c1266e029660
last_write_checksum: sha1:dab8da784a21b3d6cffb3b896c91f5d2db9d32f5
@@ -8126,8 +8162,8 @@ trackedFiles:
pristine_git_object: 0ebe5146cd24c153cb9b7655e4f502c09f7d4abd
docs/sdks/billing/README.md:
id: dc915331dd9d
- last_write_checksum: sha1:03d7f6f6b024cc9793f78a19f0d526a8469896af
- pristine_git_object: 9a752a4d17687faf85ba29f006aa5ee0b4c4a392
+ last_write_checksum: sha1:3dea219afa1894bb7f76bbbc1d3a72dedf9e263c
+ pristine_git_object: c38269a07da01854067cc02172e21cb63aad9ab4
docs/sdks/customers/README.md:
id: 9332759cffc2
last_write_checksum: sha1:9ef975ed7a810cead53f98391be01f8aafd67d7d
@@ -8146,8 +8182,8 @@ trackedFiles:
pristine_git_object: ac31687aa9e81d6776ab4933d0ad07afeff8a6d0
docs/sdks/invoices/README.md:
id: a3f76a06eded
- last_write_checksum: sha1:ae8edc2aba3941f6b2a609b54e5d9cd0eb63b66e
- pristine_git_object: d9af94d7ea5628a335b6251038e06aa8d781fc01
+ last_write_checksum: sha1:b6d6f20c67fa4f9fff5cf635c03b2fd6ca1106b1
+ pristine_git_object: ed4a91386f5392a4cc99950a5324b60b1364b670
docs/sdks/keys/README.md:
id: 9a567cd0daf2
last_write_checksum: sha1:26f46e8f7700547819773dd51af269dcb5e7445e
@@ -8158,8 +8194,8 @@ trackedFiles:
pristine_git_object: 4f442e8316cf5791b2b368511aefcb29e1231a89
docs/sdks/plans/README.md:
id: 2d8c741fff57
- last_write_checksum: sha1:c4c55cea35447b0ba4d59ecc699017f7973a6dc1
- pristine_git_object: a2f4b86c794b3d65702940a04ae81952939b7e30
+ last_write_checksum: sha1:6f6b6d68c6227ab99703c25e84902fd0f1393a70
+ pristine_git_object: 58d7d4f5bc82ecbdc759145aa5e4f4f83ebd4e03
docs/sdks/platform/README.md:
id: b66219e9cd4d
last_write_checksum: sha1:2b78aeff4d3c1023b1c461eba75a15fe864c04f9
@@ -8230,8 +8266,8 @@ trackedFiles:
pristine_git_object: c335cfcdd5ac114b1f647246236b69442ecbcdb2
src/funcs/billing-create-schedule.ts:
id: fd662bfcdc10
- last_write_checksum: sha1:ceb4cbf644d160c4ac308d9e0fbd814acbd03ee6
- pristine_git_object: 7e2d5f8e1c2cce12270d45b6f09c451d01322f48
+ last_write_checksum: sha1:07837e3de85df705abcfdaba608bb155e38d3979
+ pristine_git_object: 4f6d0f58925596e2ad4f79562a59070e86b19d72
src/funcs/billing-import.ts:
id: ff582ad4629f
last_write_checksum: sha1:673851739184e659789772519b9bc01485a21d79
@@ -8344,6 +8380,10 @@ trackedFiles:
id: d2de3d7decac
last_write_checksum: sha1:ab1ff29e5b73356e4a0fee309aeb199570353edf
pristine_git_object: 0e8b430aae0d2ff7f1f12c3b0d08b86e70dc814a
+ src/funcs/invoices-insert.ts:
+ id: 9cf6fe8e5468
+ last_write_checksum: sha1:6fd8533b062c61d82c292e6be77cbf8b23676877
+ pristine_git_object: a62c9bd95aa6ecef2ff9cef8923bc4d0120fd5aa
src/funcs/invoices-list.ts:
id: 78f1278b5014
last_write_checksum: sha1:bc7afae26b246e0c1b0c4c6d2edda8ccb8095345
@@ -8386,8 +8426,8 @@ trackedFiles:
pristine_git_object: 8fa31bd78e12c73e1bef0dbbf654d44f9aa2ffe3
src/funcs/plans-update.ts:
id: 86e469e08973
- last_write_checksum: sha1:11d8fac066cbe767a85a3011b595fc2810b6837d
- pristine_git_object: e710f9e979756058d6cf0e036fb3232e5cf59ac0
+ last_write_checksum: sha1:88f88761210e842454d0410d5d43e707ee731e15
+ pristine_git_object: e979aed821b2a64bbb18e831876e556d97470c86
src/funcs/platform-get-revenue-cat-keys.ts:
id: 3f6df7d12152
last_write_checksum: sha1:ea7b7e7d13cec4a7dc9dfb20431bc67e91f6efb6
@@ -8514,8 +8554,8 @@ trackedFiles:
pristine_git_object: 3675cd4e8ad374d6b2865e2160e6ab8a606943c2
src/models/attach-op.ts:
id: 83ed65c26ab4
- last_write_checksum: sha1:1c7c242660dcd37fc4d0bbcb8458bfeb89abe55e
- pristine_git_object: 27af21b0c93c2f56d5c0ce9d973f7db9acc54107
+ last_write_checksum: sha1:ff0a94e9f6c29fd71703a9a455d3227009eb85b0
+ pristine_git_object: 932eab043933c084dab19dd83462f0ac1dec8f28
src/models/autumn-default-error.ts:
id: 2528aa7886eb
last_write_checksum: sha1:4cce18f91be3262ada7d11dcd6326544e2341b58
@@ -8534,8 +8574,8 @@ trackedFiles:
pristine_git_object: 16f66886fcadb22a9dc6d44137aea76959579d5c
src/models/billing-update-op.ts:
id: e7371769c7ca
- last_write_checksum: sha1:49713a284065b0eb76345d85048833cf3668aa53
- pristine_git_object: 4a3ed6d731d884eb263c2ad8bf0acedbc4ca4d94
+ last_write_checksum: sha1:4a82130233bdcdab80827abf46871b31c111afe9
+ pristine_git_object: 2566f4ee4305f072ac239321b073f4cb9edb7827
src/models/check-op.ts:
id: 42085bda016a
last_write_checksum: sha1:b9a254aa82a4080ace1471a3241af7cef8954af8
@@ -8554,20 +8594,20 @@ trackedFiles:
pristine_git_object: cc88593cb431fd33f22679a5eabae1eb9705c384
src/models/create-plan-op.ts:
id: e094d152f358
- last_write_checksum: sha1:cbe3338bb5882342e72c70d11e959c7d64f53226
- pristine_git_object: dec4d66470226520d29fb6419279174bb030a0b5
+ last_write_checksum: sha1:8a3258ec202c073a718d8cbf87196953c4ede49e
+ pristine_git_object: 24f80cae278bf0095490e85766fd387f22dcc6df
src/models/create-referral-code-op.ts:
id: 745cd70e7a69
last_write_checksum: sha1:01ce64d29c3bd84e0c9bf1e6a979e7e493f10d67
pristine_git_object: d979198ac227f8e5731e5ca5e2e34b55d88da348
src/models/create-schedule-op.ts:
id: 68442c0abd75
- last_write_checksum: sha1:4bfd2bebe7b862aa3f899e5f927a986a61ba34cd
- pristine_git_object: 60019d0e9978f44819ebc48da8beb2aebde07e33
+ last_write_checksum: sha1:fa8e7d5c19015da04eeebca7c631b09dfa9f0186
+ pristine_git_object: ed42c4b86bb279cad475376fdfe03bd30b245bc4
src/models/customer-data.ts:
id: 04dac7ee392e
- last_write_checksum: sha1:1b30a1e254d56693f3284443be6f349ffa4ec484
- pristine_git_object: d7b53b00bc0218a248e5e7496d53e9558ce6d04c
+ last_write_checksum: sha1:39275a61f77a7323d604fc327f515b13525e5f8d
+ pristine_git_object: 85e02d59cd1a0382adab8093815facb8b170eb5e
src/models/customer-expand.ts:
id: 0792f23b0bc0
last_write_checksum: sha1:0c9f3e2b934fcf8371c490ee4ca8cb9450c8db66
@@ -8614,12 +8654,12 @@ trackedFiles:
pristine_git_object: 0097008c9b033d8069530b1b473a5a6f890b002e
src/models/get-or-create-customer-op.ts:
id: 46f8f65a57f2
- last_write_checksum: sha1:e102121fa894e6394082fba700f095de7ee70fe4
- pristine_git_object: 8bd7e9a076e3e57044bcb50baff6b24db7b503d2
+ last_write_checksum: sha1:b9082e3198c9ceb5e970247d156ca7367f83f3c4
+ pristine_git_object: de248bb3223837499ef0aad45ec21ecbf0d3a9f8
src/models/get-plan-op.ts:
id: 91c8f8dda7c8
- last_write_checksum: sha1:91ff2a679af58b8ea4d5955f7936c8b32e076eb6
- pristine_git_object: deed3d3a3234fea40e21a78a4f4342ae33d4dc47
+ last_write_checksum: sha1:21dc123819b9a7128a9e88c93d876bef747dcb92
+ pristine_git_object: bf5e1279f338aac5b7e9ee77744f4b1ac51e7dbe
src/models/get-revenue-cat-keys-op.ts:
id: 2d8e9e87f071
last_write_checksum: sha1:e1c1f2eaf75a3db81b4800c63cbe2ea6e5b1fa56
@@ -8634,8 +8674,12 @@ trackedFiles:
pristine_git_object: bd7047c94965f6154cbf8657c5a6a9ddabdf174d
src/models/index.ts:
id: f93644b0f37e
- last_write_checksum: sha1:b5ce3e2b74d073f57c71d0c1f8c77087fb0bd3bf
- pristine_git_object: 108897300b1dd238af6135185a1afbe30a8f966b
+ last_write_checksum: sha1:7a0af0ca2ac5d11e3c9c62b9ecf384c1ce609985
+ pristine_git_object: c38815d4e216d9748638c1f2cff855d1917499ce
+ src/models/insert-invoices-op.ts:
+ id: d5b32875066f
+ last_write_checksum: sha1:414244eb6712967c72c16c4a656fe4688a759bc3
+ pristine_git_object: b48d34e3cc1ff2708d3fcd435a4c30fea4eabbd8
src/models/link-revenue-cat-op.ts:
id: 6cc62c90b574
last_write_checksum: sha1:a2f11a5efb037c6c640da26f07b2a407b7dfffad
@@ -8658,12 +8702,12 @@ trackedFiles:
pristine_git_object: cc0e37a190877dc4b5c825bf4c20b40fd7600211
src/models/list-invoices-op.ts:
id: 475d8df302da
- last_write_checksum: sha1:50e321760b823efb3204bcbd753f08a82a10c147
- pristine_git_object: f071ebd0f9b72b3e6ee86945969c5cb0e0a465ad
+ last_write_checksum: sha1:61e132b865f946013cdfd4b74023f08d7314b9eb
+ pristine_git_object: 006dc30d4ca20cfa16954578ac6dbf7b93f912e4
src/models/list-plans-op.ts:
id: 513cde894485
- last_write_checksum: sha1:66917a089b45523f8fc4bab66dc13efe97a72513
- pristine_git_object: 78221e2253ad64bd0aeaab5655bb8101cb006b43
+ last_write_checksum: sha1:435357f35e627b22f125798e227c3840e289cdab
+ pristine_git_object: 41c54675daefa730285d1b30c36d8597dc62e3b8
src/models/list-rewards-op.ts:
id: 1fa339fa50fb
last_write_checksum: sha1:281da10de3dd1399ed63a3910db0cae7bb5aa7d9
@@ -8686,12 +8730,12 @@ trackedFiles:
pristine_git_object: 4a9318890026bc6e67a8a5b65dc596ae7f25f855
src/models/plan.ts:
id: 9e9698a64fe7
- last_write_checksum: sha1:916b9112944aeb7b21e0b452dfbde12ac4727f0d
- pristine_git_object: 861af25dede9ad35d72ab1c3e9f81c17e9f3dad7
+ last_write_checksum: sha1:6e30f35b9f788e35b6df9b1dea708764b5f79357
+ pristine_git_object: 178af66132697f9fb1810a8e7c0d121b2e106122
src/models/preview-attach-op.ts:
id: 3efc6e3443a7
- last_write_checksum: sha1:7d10d27a284e933c4ee3f660d30835f4d9abf544
- pristine_git_object: 23b0f12466d2007bc84d32f2fab2227545905d4e
+ last_write_checksum: sha1:e6f6846714c4b38219e00bcd4558ecd749a12f45
+ pristine_git_object: b395a92c8537426f403b1ead01b44cf423e29ed5
src/models/preview-multi-attach-op.ts:
id: e4847dc281a6
last_write_checksum: sha1:89ea23151299f10f8b45aaef20e6297145653422
@@ -8702,8 +8746,8 @@ trackedFiles:
pristine_git_object: 8cc0bd0a02d8c594be41c3cda6e0c88e69428ef1
src/models/preview-update-op.ts:
id: fcbbbf3b22ac
- last_write_checksum: sha1:0a0e87f79cdb66fdba29d99120ffe2dfe05196a8
- pristine_git_object: 6e2a2d1cfbf2c5849117db14c73b786fec3c9bc2
+ last_write_checksum: sha1:0223f3a80876635299e21c8f008f4001a6e89304
+ pristine_git_object: 2ddf0ba99e7f88a01a3c4e4fcc2d138e393cb33c
src/models/redeem-referral-code-op.ts:
id: 511bf73dc4c6
last_write_checksum: sha1:9ab6622018c82175ea98d2b26eadb4abf08f441a
@@ -8738,8 +8782,8 @@ trackedFiles:
pristine_git_object: 3774cc1e9bbb80ac592990aa86f8d4a38ee51f29
src/models/setup-payment-op.ts:
id: 0e97e999ff3c
- last_write_checksum: sha1:9208206c4bdbc1c944ceafbf5251d0e508270f0a
- pristine_git_object: d806443282d3055ad5d25069b476396fddbd1a40
+ last_write_checksum: sha1:57b8367c897264b7103a54a613f8950a0298ce78
+ pristine_git_object: 65bab2b342c53a4230799f5137e4562d7ae00a40
src/models/sync-revenue-cat-op.ts:
id: bf3c25067f7c
last_write_checksum: sha1:a5992af2f44badc20be2d55c8091ed4c22dfc903
@@ -8758,8 +8802,8 @@ trackedFiles:
pristine_git_object: 8c47bb5b8cf5546e12fffaddcab36022c1f2ea4a
src/models/update-customer-op.ts:
id: 5d226d30d8e4
- last_write_checksum: sha1:0f000a49837251f60bd105dccea642fdd0f1c8c4
- pristine_git_object: 98437d4b30bbe7b25e8eebe2565d9c97425cf5d4
+ last_write_checksum: sha1:c982aacf8aaf8a25483df2073a757daa7522c67f
+ pristine_git_object: 2ea2f18148ef688db524c70fa3a155b092b7365e
src/models/update-entity-op.ts:
id: c3fdb6479f02
last_write_checksum: sha1:0b11754f6c14f9d5c4c8c688cff3846dd3571782
@@ -8770,16 +8814,16 @@ trackedFiles:
pristine_git_object: 1564c4312ff7944eaaaa5b8e8a80d4877b97f134
src/models/update-plan-op.ts:
id: 54b4f842d3b2
- last_write_checksum: sha1:dffac6acd6d5e84891606aec0e3fd2bfd2eb9744
- pristine_git_object: 0f847edb903062ee025a00d0c1df0ca3dccad6ae
+ last_write_checksum: sha1:20d43f8b0c1f0d28b22a5485a40916480f26546b
+ pristine_git_object: 6f2a857f17ed2e6115c4031a15b3a603b266326c
src/sdk/balances.ts:
id: 9ad229cb9d64
last_write_checksum: sha1:3e195bbaeea3a9bc5afd4095d92949f3cad0956e
pristine_git_object: 571de419ea3321d79acec4bddbb46b1580007115
src/sdk/billing.ts:
id: 10905058c4ad
- last_write_checksum: sha1:1991c2717459c706cd60060768d70f8bfa725e53
- pristine_git_object: ded8bbc8c8c9f94ed0baa4590024b79735af6e69
+ last_write_checksum: sha1:49f676179292a5de7b824cecbb70ee967f6f905e
+ pristine_git_object: 8bf3abf77f0cc26be6779670ac2e9541514a826a
src/sdk/customers.ts:
id: d33e193e0c00
last_write_checksum: sha1:9363c51bdf5a65a291f6ab33d70cf3742f5d3bbe
@@ -8802,8 +8846,8 @@ trackedFiles:
pristine_git_object: ecac2264817bb369ff2dbf0f0e9029807e67ff77
src/sdk/invoices.ts:
id: b0baec52de2f
- last_write_checksum: sha1:f1c2c93e37a9a017fbfa02373075c99d177968ea
- pristine_git_object: 76d65ab90a24eb81e1025ab723ecf147ca5ea18c
+ last_write_checksum: sha1:94f7f2bba76946238ebfeafdf61cb431df265f35
+ pristine_git_object: 2bc21cdbfb6c3a37f12243d8c6ebbeb080619b31
src/sdk/keys.ts:
id: 474a4602e64e
last_write_checksum: sha1:2e8e726339cd9e49bb9af908b9047565150e7483
@@ -8814,8 +8858,8 @@ trackedFiles:
pristine_git_object: dd751dbd93b02d1edf3fa855efe47de7755d6e67
src/sdk/plans.ts:
id: c0cb8188cdc1
- last_write_checksum: sha1:3b5ef2e13d353bda38d8758d382bb84dec18f45f
- pristine_git_object: b9f2d8052b8d7df2c2408069c3beed3331bae690
+ last_write_checksum: sha1:0c41ae6d16a32681c06a1f85fd8c7acd61514df2
+ pristine_git_object: 2c0a568fa783fb129a966c78307cc2cc92ff4ff8
src/sdk/platform.ts:
id: 86d659f7229d
last_write_checksum: sha1:831299efdeefe09a98910994e31c0ff992f3915f
@@ -9916,4 +9960,14 @@ examples:
responses:
"200":
application/json: {"list": [{"license_plan_id": "seat_plan", "parent_plan_id": "pro_plan", "license_plan_name": "Seat", "granted": 10, "usage": 3, "remaining": 7, "paid_quantity": 5}]}
+ insertInvoices:
+ speakeasy-default-insert-invoices:
+ parameters:
+ header:
+ x-api-version: "2.3.0"
+ requestBody:
+ application/json: {"invoices": [{"customer_id": "cus_123", "plan_ids": ["pro"], "stripe_id": "in_legacy_123", "processor_type": "stripe", "status": "paid", "total": 29.99, "amount_paid": 29.99, "refunded_amount": 0, "currency": "usd", "created_at": 1451606400000, "hosted_invoice_url": "https://billing.example.com/invoices/legacy-123"}]}
+ responses:
+ "200":
+ application/json: {"invoices": [{"plan_ids": ["pro"], "stripe_id": "in_legacy_123", "processor_type": "stripe", "status": "paid", "total": 29.99, "currency": "usd", "created_at": 1451606400000, "hosted_invoice_url": "https://billing.example.com/invoices/legacy-123", "id": "inv_2b3c4d5e6f7g8h", "customer_id": "cus_123", "amount_paid": 29.99, "refunded_amount": 0}]}
examplesVersion: 1.0.2
diff --git a/packages/sdk/.speakeasy/out.openapi.yaml b/packages/sdk/.speakeasy/out.openapi.yaml
index e4e0df1836..ce703e95de 100644
--- a/packages/sdk/.speakeasy/out.openapi.yaml
+++ b/packages/sdk/.speakeasy/out.openapi.yaml
@@ -74,7 +74,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -100,10 +99,14 @@ components:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -322,7 +325,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -1865,7 +1867,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -1891,10 +1892,14 @@ components:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -2048,7 +2053,6 @@ components:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -2684,7 +2688,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -2710,10 +2713,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -3011,7 +3018,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -4001,7 +4007,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -4790,7 +4795,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -4816,10 +4820,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -5036,7 +5044,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -6336,11 +6343,6 @@ paths:
propertyNames:
type: string
additionalProperties: {}
- version:
- type: integer
- minimum: 1
- maximum: 9.007199254740991e+15
- description: Pin the link to a specific version of the license plan. Omitted, an existing link keeps its pinned version and a new link resolves to the latest.
required:
- license_plan_id
description: Plans offered as assignable licenses under this plan. The full set replaces existing links.
@@ -6396,7 +6398,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -6422,10 +6423,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -7297,7 +7302,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -7323,10 +7327,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -7480,7 +7488,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -8491,7 +8498,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -8517,10 +8523,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -8674,7 +8684,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -9655,7 +9664,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -9681,10 +9689,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -9838,7 +9850,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -10152,6 +10163,7 @@ paths:
@param allVersions - Apply the update diff to all versions of this plan. Mutually exclusive with disable_version. (optional)
@param forceVersion - Force versioning even when no customers exist. Mutually exclusive with disable_version. (optional)
@param updateVariantIds - Variant plan IDs to apply this update to. Empty or omitted means no propagation. (optional)
+ @param updateLicenseParents - Parent plan versions that should receive this license-plan update. (optional)
@param variants - Additive variant updates for this base plan. Missing variants are created when name is provided. (optional)
@param isDefault - Whether this is the org's default plan. Cannot be true on a variant. (optional)
@@ -10681,11 +10693,6 @@ paths:
propertyNames:
type: string
additionalProperties: {}
- version:
- type: integer
- minimum: 1
- maximum: 9.007199254740991e+15
- description: Pin the link to a specific version of the license plan. Omitted, an existing link keeps its pinned version and a new link resolves to the latest.
required:
- license_plan_id
description: Plans offered as assignable licenses under this plan. The full set replaces existing links.
@@ -10745,7 +10752,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -10771,10 +10777,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -10951,6 +10961,23 @@ paths:
items:
type: string
description: Variant plan IDs to apply this update to. Empty or omitted means no propagation.
+ update_license_parents:
+ type: array
+ items:
+ type: object
+ properties:
+ plan_id:
+ type: string
+ minLength: 1
+ pattern: ^[a-zA-Z0-9_-]+$
+ version:
+ type: integer
+ minimum: 1
+ maximum: 9.007199254740991e+15
+ required:
+ - plan_id
+ - version
+ description: Parent plan versions that should receive this license-plan update.
variants:
type: array
items:
@@ -11273,7 +11300,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -11299,10 +11325,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -12162,7 +12192,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -12188,10 +12217,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -12345,7 +12378,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -14252,7 +14284,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -14278,10 +14309,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -14951,7 +14986,7 @@ paths:
@example
```typescript
// Schedule a transition from a trial plan to a paid plan
- const response = await client.billing.createSchedule({ customerId: "cus_123", phases: [{"startsAt":1784299196316,"plans":[{"planId":"trial_plan"}]},{"startsAt":1785508796316,"plans":[{"planId":"pro_plan"}]}] });
+ const response = await client.billing.createSchedule({ customerId: "cus_123", phases: [{"startsAt":1784744849888,"plans":[{"planId":"trial_plan"}]},{"startsAt":1785954449888,"plans":[{"planId":"pro_plan"}]}] });
```
@param customerId - The ID of the customer to create the schedule for.
@@ -15515,7 +15550,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -15541,10 +15575,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -16142,7 +16180,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -16168,10 +16205,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -17699,7 +17740,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -17725,10 +17765,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -20237,7 +20281,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -20263,10 +20306,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -21471,7 +21518,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -21497,10 +21543,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -23617,7 +23667,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -23643,10 +23692,14 @@ paths:
type: number
minimum: 1
description: Maximum number of auto top-ups allowed within the interval.
+ count:
+ type: number
+ minimum: 0
+ description: Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
required:
- interval
- limit
- description: Optional rate limit to cap how often auto top-ups occur.
+ description: Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
invoice_mode:
type: boolean
description: When true, auto top-up creates a send_invoice invoice instead of auto-charging.
@@ -25416,7 +25469,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -26128,7 +26180,6 @@ paths:
description: Whether auto top-up is enabled.
threshold:
type: number
- minimum: 0
description: When the balance drops below this threshold, an auto top-up will be purchased.
quantity:
type: number
@@ -27655,6 +27706,193 @@ paths:
x-speakeasy-name-override: aggregate
parameters:
- *a1
+ /v1/invoices.insert:
+ post:
+ operationId: insertInvoices
+ description: Inserts or updates up to 500 historical invoices without reading or mutating the billing processor.
+ tags:
+ - invoices
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ invoices:
+ type: array
+ maxItems: 500
+ items:
+ type: object
+ properties:
+ customer_id:
+ type: string
+ description: The customer this invoice belongs to.
+ plan_ids:
+ type: array
+ items:
+ type: string
+ default: []
+ description: Plan IDs represented by this invoice.
+ stripe_id:
+ type: string
+ description: The processor's stable invoice ID.
+ processor_type:
+ enum:
+ - stripe
+ - revenuecat
+ type: string
+ default: stripe
+ description: The billing processor that owns this invoice.
+ status:
+ enum:
+ - draft
+ - open
+ - void
+ - paid
+ - uncollectible
+ type: string
+ description: The invoice status.
+ total:
+ type: number
+ description: The invoice total in major currency units.
+ amount_paid:
+ anyOf:
+ - type: number
+ - type: "null"
+ default: null
+ description: The amount paid in major currency units.
+ refunded_amount:
+ type: number
+ default: 0
+ description: The refunded amount in major currency units.
+ currency:
+ type: string
+ description: The currency code. Defaults to the organization's default currency.
+ created_at:
+ type: number
+ description: The invoice creation timestamp in milliseconds.
+ hosted_invoice_url:
+ anyOf:
+ - type: string
+ - type: "null"
+ default: null
+ description: The hosted invoice URL, when available.
+ required:
+ - customer_id
+ - stripe_id
+ - status
+ - total
+ - created_at
+ description: Invoices to insert or update, in response order.
+ required:
+ - invoices
+ title: InsertInvoicesParams
+ examples:
+ - invoices:
+ - customer_id: cus_123
+ plan_ids:
+ - pro
+ stripe_id: in_legacy_123
+ processor_type: stripe
+ status: paid
+ total: 29.99
+ amount_paid: 29.99
+ refunded_amount: 0
+ currency: usd
+ created_at: 1451606400000
+ hosted_invoice_url: https://billing.example.com/invoices/legacy-123
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ invoices:
+ type: array
+ items:
+ type: object
+ properties:
+ plan_ids:
+ type: array
+ items:
+ type: string
+ description: Array of plan IDs included in this invoice
+ stripe_id:
+ type: string
+ description: The Stripe invoice ID
+ processor_type:
+ enum:
+ - stripe
+ - revenuecat
+ type: string
+ default: stripe
+ description: The billing processor that owns this invoice.
+ status:
+ type: string
+ description: The status of the invoice
+ total:
+ type: number
+ description: The total amount of the invoice
+ currency:
+ type: string
+ description: The currency code for the invoice
+ created_at:
+ type: number
+ description: Timestamp when the invoice was created
+ hosted_invoice_url:
+ anyOf:
+ - type: string
+ - type: "null"
+ description: URL to the Stripe-hosted invoice page
+ id:
+ type: string
+ description: The Autumn invoice ID.
+ customer_id:
+ type: string
+ description: The customer this invoice belongs to.
+ amount_paid:
+ anyOf:
+ - type: number
+ - type: "null"
+ description: The amount paid in major currency units.
+ refunded_amount:
+ type: number
+ description: The refunded amount in major currency units.
+ required:
+ - plan_ids
+ - stripe_id
+ - status
+ - total
+ - currency
+ - created_at
+ - id
+ - customer_id
+ - amount_paid
+ - refunded_amount
+ description: Inserted or updated invoices in request order.
+ required:
+ - invoices
+ examples:
+ - invoices:
+ - id: inv_2b3c4d5e6f7g8h
+ customer_id: cus_123
+ plan_ids:
+ - pro
+ stripe_id: in_legacy_123
+ processor_type: stripe
+ status: paid
+ total: 29.99
+ amount_paid: 29.99
+ refunded_amount: 0
+ currency: usd
+ created_at: 1451606400000
+ hosted_invoice_url: https://billing.example.com/invoices/legacy-123
+ x-speakeasy-name-override: insert
+ parameters:
+ - *a1
/v1/invoices.list:
post:
operationId: listInvoices
diff --git a/packages/sdk/.speakeasy/workflow.lock b/packages/sdk/.speakeasy/workflow.lock
index 0481d2d3bb..188713bfc4 100644
--- a/packages/sdk/.speakeasy/workflow.lock
+++ b/packages/sdk/.speakeasy/workflow.lock
@@ -2,8 +2,8 @@ speakeasyVersion: 1.762.0
sources:
Autumn API:
sourceNamespace: autumn-api
- sourceRevisionDigest: sha256:efac7660d9aca386df219c5028dccc61518f8e3a18e6c99ab75367153c0ba9c3
- sourceBlobDigest: sha256:d4f45ab94537037f30134394639439fc78723c17fdc138cfa8419ffae7070f5e
+ sourceRevisionDigest: sha256:55050d94598a33577d6469c69e6d7ce6791e2726c5396aad5be09c91f16fd7c2
+ sourceBlobDigest: sha256:6257e01399e67c966be550f74194330b0ca34153dbbdb7fb17c53b7036250069
tags:
- latest
- 2.3.0
@@ -18,10 +18,10 @@ targets:
autumn:
source: Autumn API
sourceNamespace: autumn-api
- sourceRevisionDigest: sha256:efac7660d9aca386df219c5028dccc61518f8e3a18e6c99ab75367153c0ba9c3
- sourceBlobDigest: sha256:d4f45ab94537037f30134394639439fc78723c17fdc138cfa8419ffae7070f5e
+ sourceRevisionDigest: sha256:55050d94598a33577d6469c69e6d7ce6791e2726c5396aad5be09c91f16fd7c2
+ sourceBlobDigest: sha256:6257e01399e67c966be550f74194330b0ca34153dbbdb7fb17c53b7036250069
codeSamplesNamespace: autumn-api-typescript-code-samples
- codeSamplesRevisionDigest: sha256:dbcdc059964564ceec7796292caeaae003af30ed17ef62a575d2f8aa2186291e
+ codeSamplesRevisionDigest: sha256:f028d653180d5385753af8453e507b8eece20d519f067438c45bbb88025674ab
autumn-python:
source: Autumn API Stripped
sourceNamespace: autumn-api-stripped
diff --git a/packages/sdk/README.md b/packages/sdk/README.md
index 04d6302478..e0357fdade 100644
--- a/packages/sdk/README.md
+++ b/packages/sdk/README.md
@@ -313,7 +313,7 @@ Use this endpoint to schedule future plan changes (e.g. switch from a trial plan
@example
```typescript
// Schedule a transition from a trial plan to a paid plan
-const response = await client.billing.createSchedule({ customerId: "cus_123", phases: [{"startsAt":1784299196316,"plans":[{"planId":"trial_plan"}]},{"startsAt":1785508796316,"plans":[{"planId":"pro_plan"}]}] });
+const response = await client.billing.createSchedule({ customerId: "cus_123", phases: [{"startsAt":1784744849888,"plans":[{"planId":"trial_plan"}]},{"startsAt":1785954449888,"plans":[{"planId":"pro_plan"}]}] });
```
@param customerId - The ID of the customer to create the schedule for.
@@ -793,6 +793,7 @@ const response = await client.features.delete({ featureId: "old-feature" });
### [Invoices](docs/sdks/invoices/README.md)
+* [insert](docs/sdks/invoices/README.md#insert) - Inserts or updates up to 500 historical invoices without reading or mutating the billing processor.
* [list](docs/sdks/invoices/README.md#list) - Lists invoices with cursor pagination and optional filters (customer, entity, status, processor). Pass `start_cursor: ""` (or omit) for the first page; use `next_cursor` from a prior response for subsequent pages.
### [Keys](docs/sdks/keys/README.md)
@@ -913,7 +914,7 @@ Use this endpoint to schedule future plan changes (e.g. switch from a trial plan
@example
```typescript
// Schedule a transition from a trial plan to a paid plan
-const response = await client.billing.createSchedule({ customerId: "cus_123", phases: [{"startsAt":1784299196316,"plans":[{"planId":"trial_plan"}]},{"startsAt":1785508796316,"plans":[{"planId":"pro_plan"}]}] });
+const response = await client.billing.createSchedule({ customerId: "cus_123", phases: [{"startsAt":1784744849888,"plans":[{"planId":"trial_plan"}]},{"startsAt":1785954449888,"plans":[{"planId":"pro_plan"}]}] });
```
@param customerId - The ID of the customer to create the schedule for.
@@ -1410,6 +1411,7 @@ const response = await client.features.update({ featureId: "deprecated-feature",
@param newFeatureId - The new ID of the feature. Feature ID can only be updated if it's not being used by any customers. (optional)
@returns The updated feature object.
+- [`invoicesInsert`](docs/sdks/invoices/README.md#insert) - Inserts or updates up to 500 historical invoices without reading or mutating the billing processor.
- [`invoicesList`](docs/sdks/invoices/README.md#list) - Lists invoices with cursor pagination and optional filters (customer, entity, status, processor). Pass `start_cursor: ""` (or omit) for the first page; use `next_cursor` from a prior response for subsequent pages.
- [`keysMint`](docs/sdks/keys/README.md#mint) - Mints a per-customer token (a scoped `am_jwt_` credential) so a downstream / self-hosted app can call Autumn directly without your secret key. Returns a short-lived access token plus a rotating refresh token, both bound to the given customer. Authenticated with your secret key.
- [`keysRefresh`](docs/sdks/keys/README.md#refresh) - Exchanges a refresh token (sent as the Bearer credential) for a freshly rotated access + refresh pair. Self-service for the token holder — no secret key required. The previous refresh token is honored for one rotation as a grace window; replaying an older one revokes the customer's tokens.
diff --git a/packages/sdk/src/funcs/billing-create-schedule.ts b/packages/sdk/src/funcs/billing-create-schedule.ts
index 7e2d5f8e1c..4f6d0f5892 100644
--- a/packages/sdk/src/funcs/billing-create-schedule.ts
+++ b/packages/sdk/src/funcs/billing-create-schedule.ts
@@ -34,7 +34,7 @@ import { Result } from "../types/fp.js";
* @example
* ```typescript
* // Schedule a transition from a trial plan to a paid plan
- * const response = await client.billing.createSchedule({ customerId: "cus_123", phases: [{"startsAt":1784299196316,"plans":[{"planId":"trial_plan"}]},{"startsAt":1785508796316,"plans":[{"planId":"pro_plan"}]}] });
+ * const response = await client.billing.createSchedule({ customerId: "cus_123", phases: [{"startsAt":1784744849888,"plans":[{"planId":"trial_plan"}]},{"startsAt":1785954449888,"plans":[{"planId":"pro_plan"}]}] });
* ```
*
* @param customerId - The ID of the customer to create the schedule for.
diff --git a/packages/sdk/src/funcs/invoices-insert.ts b/packages/sdk/src/funcs/invoices-insert.ts
new file mode 100644
index 0000000000..a62c9bd95a
--- /dev/null
+++ b/packages/sdk/src/funcs/invoices-insert.ts
@@ -0,0 +1,165 @@
+/*
+ * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.
+ */
+
+import * as z from "zod/v4-mini";
+import { AutumnCore } from "../core.js";
+import { encodeJSON, encodeSimple } from "../lib/encodings.js";
+import { matchStatusCode } from "../lib/http.js";
+import * as M from "../lib/matchers.js";
+import { compactMap } from "../lib/primitives.js";
+import { safeParse } from "../lib/schemas.js";
+import { RequestOptions } from "../lib/sdks.js";
+import { extractSecurity, resolveGlobalSecurity } from "../lib/security.js";
+import { pathToFunc } from "../lib/url.js";
+import { AutumnError } from "../models/autumn-error.js";
+import {
+ ConnectionError,
+ InvalidRequestError,
+ RequestAbortedError,
+ RequestTimeoutError,
+ UnexpectedClientError,
+} from "../models/http-client-errors.js";
+import * as models from "../models/index.js";
+import { ResponseValidationError } from "../models/response-validation-error.js";
+import { SDKValidationError } from "../models/sdk-validation-error.js";
+import { APICall, APIPromise } from "../types/async.js";
+import { Result } from "../types/fp.js";
+
+/**
+ * Inserts or updates up to 500 historical invoices without reading or mutating the billing processor.
+ */
+export function invoicesInsert(
+ client: AutumnCore,
+ request: models.InsertInvoicesParams,
+ options?: RequestOptions,
+): APIPromise<
+ Result<
+ models.InsertInvoicesResponse,
+ | AutumnError
+ | ResponseValidationError
+ | ConnectionError
+ | RequestAbortedError
+ | RequestTimeoutError
+ | InvalidRequestError
+ | UnexpectedClientError
+ | SDKValidationError
+ >
+> {
+ return new APIPromise($do(
+ client,
+ request,
+ options,
+ ));
+}
+
+async function $do(
+ client: AutumnCore,
+ request: models.InsertInvoicesParams,
+ options?: RequestOptions,
+): Promise<
+ [
+ Result<
+ models.InsertInvoicesResponse,
+ | AutumnError
+ | ResponseValidationError
+ | ConnectionError
+ | RequestAbortedError
+ | RequestTimeoutError
+ | InvalidRequestError
+ | UnexpectedClientError
+ | SDKValidationError
+ >,
+ APICall,
+ ]
+> {
+ const parsed = safeParse(
+ request,
+ (value) => z.parse(models.InsertInvoicesParams$outboundSchema, value),
+ "Input validation failed",
+ );
+ if (!parsed.ok) {
+ return [parsed, { status: "invalid" }];
+ }
+ const payload = parsed.value;
+ const body = encodeJSON("body", payload, { explode: true });
+
+ const path = pathToFunc("/v1/invoices.insert")();
+
+ const headers = new Headers(compactMap({
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ "x-api-version": encodeSimple(
+ "x-api-version",
+ client._options.xApiVersion,
+ { explode: false, charEncoding: "none" },
+ ),
+ }));
+
+ const secConfig = await extractSecurity(client._options.secretKey);
+ const securityInput = secConfig == null ? {} : { secretKey: secConfig };
+ const requestSecurity = resolveGlobalSecurity(securityInput);
+
+ const context = {
+ options: client._options,
+ baseURL: options?.serverURL ?? client._baseURL ?? "",
+ operationID: "insertInvoices",
+ oAuth2Scopes: null,
+
+ resolvedSecurity: requestSecurity,
+
+ securitySource: client._options.secretKey,
+ retryConfig: options?.retries
+ || client._options.retryConfig
+ || { strategy: "none" },
+ retryCodes: options?.retryCodes || ["429", "500", "502", "503", "504"],
+ };
+
+ const requestRes = client._createRequest(context, {
+ security: requestSecurity,
+ method: "POST",
+ baseURL: options?.serverURL,
+ path: path,
+ headers: headers,
+ body: body,
+ userAgent: client._options.userAgent,
+ timeoutMs: options?.timeoutMs || client._options.timeoutMs || -1,
+ }, options);
+ if (!requestRes.ok) {
+ return [requestRes, { status: "invalid" }];
+ }
+ const req = requestRes.value;
+
+ const doResult = await client._do(req, {
+ context,
+ isErrorStatusCode: (statusCode: number) =>
+ matchStatusCode({ status: statusCode } as Response, ["4XX", "5XX"]),
+ retryConfig: context.retryConfig,
+ retryCodes: context.retryCodes,
+ });
+ if (!doResult.ok) {
+ return [doResult, { status: "request-error", request: req }];
+ }
+ const response = doResult.value;
+
+ const [result] = await M.match<
+ models.InsertInvoicesResponse,
+ | AutumnError
+ | ResponseValidationError
+ | ConnectionError
+ | RequestAbortedError
+ | RequestTimeoutError
+ | InvalidRequestError
+ | UnexpectedClientError
+ | SDKValidationError
+ >(
+ M.json(200, models.InsertInvoicesResponse$inboundSchema),
+ M.fail("4XX"),
+ M.fail("5XX"),
+ )(response, req);
+ if (!result.ok) {
+ return [result, { status: "complete", request: req, response }];
+ }
+
+ return [result, { status: "complete", request: req, response }];
+}
diff --git a/packages/sdk/src/funcs/plans-update.ts b/packages/sdk/src/funcs/plans-update.ts
index e710f9e979..e979aed821 100644
--- a/packages/sdk/src/funcs/plans-update.ts
+++ b/packages/sdk/src/funcs/plans-update.ts
@@ -79,6 +79,7 @@ import { Result } from "../types/fp.js";
* @param allVersions - Apply the update diff to all versions of this plan. Mutually exclusive with disable_version. (optional)
* @param forceVersion - Force versioning even when no customers exist. Mutually exclusive with disable_version. (optional)
* @param updateVariantIds - Variant plan IDs to apply this update to. Empty or omitted means no propagation. (optional)
+ * @param updateLicenseParents - Parent plan versions that should receive this license-plan update. (optional)
* @param variants - Additive variant updates for this base plan. Missing variants are created when name is provided. (optional)
* @param isDefault - Whether this is the org's default plan. Cannot be true on a variant. (optional)
*
diff --git a/packages/sdk/src/models/attach-op.ts b/packages/sdk/src/models/attach-op.ts
index 27af21b0c9..932eab0439 100644
--- a/packages/sdk/src/models/attach-op.ts
+++ b/packages/sdk/src/models/attach-op.ts
@@ -752,7 +752,7 @@ export type AttachPurchaseLimitInterval = ClosedEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type AttachPurchaseLimit = {
/**
@@ -767,6 +767,10 @@ export type AttachPurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type AttachAutoTopup = {
@@ -787,7 +791,7 @@ export type AttachAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: AttachPurchaseLimit | undefined;
/**
@@ -2602,6 +2606,7 @@ export type AttachPurchaseLimit$Outbound = {
interval: string;
interval_count: number;
limit: number;
+ count?: number | undefined;
};
/** @internal */
@@ -2613,6 +2618,7 @@ export const AttachPurchaseLimit$outboundSchema: z.ZodMiniType<
interval: AttachPurchaseLimitInterval$outboundSchema,
intervalCount: z._default(z.number(), 1),
limit: z.number(),
+ count: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/billing-update-op.ts b/packages/sdk/src/models/billing-update-op.ts
index 4a3ed6d731..2566f4ee43 100644
--- a/packages/sdk/src/models/billing-update-op.ts
+++ b/packages/sdk/src/models/billing-update-op.ts
@@ -768,7 +768,7 @@ export type BillingUpdatePurchaseLimitInterval = ClosedEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type BillingUpdatePurchaseLimit = {
/**
@@ -783,6 +783,10 @@ export type BillingUpdatePurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type BillingUpdateAutoTopup = {
@@ -803,7 +807,7 @@ export type BillingUpdateAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: BillingUpdatePurchaseLimit | undefined;
/**
@@ -2634,6 +2638,7 @@ export type BillingUpdatePurchaseLimit$Outbound = {
interval: string;
interval_count: number;
limit: number;
+ count?: number | undefined;
};
/** @internal */
@@ -2645,6 +2650,7 @@ export const BillingUpdatePurchaseLimit$outboundSchema: z.ZodMiniType<
interval: BillingUpdatePurchaseLimitInterval$outboundSchema,
intervalCount: z._default(z.number(), 1),
limit: z.number(),
+ count: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/create-plan-op.ts b/packages/sdk/src/models/create-plan-op.ts
index dec4d66470..24f80cae27 100644
--- a/packages/sdk/src/models/create-plan-op.ts
+++ b/packages/sdk/src/models/create-plan-op.ts
@@ -742,10 +742,6 @@ export type CreatePlanLicense = {
prepaidOnly?: boolean | undefined;
customize?: CreatePlanLicenseCustomize | null | undefined;
metadata?: { [k: string]: any } | undefined;
- /**
- * Pin the link to a specific version of the license plan. Omitted, an existing link keeps its pinned version and a new link resolves to the latest.
- */
- version?: number | undefined;
};
/**
@@ -824,7 +820,7 @@ export type CreatePlanPurchaseLimitIntervalRequestBody = ClosedEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type CreatePlanPurchaseLimitRequest = {
/**
@@ -839,6 +835,10 @@ export type CreatePlanPurchaseLimitRequest = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type CreatePlanAutoTopupRequest = {
@@ -859,7 +859,7 @@ export type CreatePlanAutoTopupRequest = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: CreatePlanPurchaseLimitRequest | undefined;
/**
@@ -1972,7 +1972,7 @@ export type CreatePlanVariantDetailsPurchaseLimitInterval = OpenEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type CreatePlanVariantDetailsPurchaseLimit = {
/**
@@ -1987,6 +1987,10 @@ export type CreatePlanVariantDetailsPurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type CreatePlanVariantDetailsAutoTopup = {
@@ -2007,7 +2011,7 @@ export type CreatePlanVariantDetailsAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: CreatePlanVariantDetailsPurchaseLimit | undefined;
/**
@@ -3567,7 +3571,6 @@ export type CreatePlanLicense$Outbound = {
prepaid_only?: boolean | undefined;
customize?: CreatePlanLicenseCustomize$Outbound | null | undefined;
metadata?: { [k: string]: any } | undefined;
- version?: number | undefined;
};
/** @internal */
@@ -3583,7 +3586,6 @@ export const CreatePlanLicense$outboundSchema: z.ZodMiniType<
z.nullable(z.lazy(() => CreatePlanLicenseCustomize$outboundSchema)),
),
metadata: z.optional(z.record(z.string(), z.any())),
- version: z.optional(z.int()),
}),
z.transform((v) => {
return remap$(v, {
@@ -3690,6 +3692,7 @@ export type CreatePlanPurchaseLimitRequest$Outbound = {
interval: string;
interval_count: number;
limit: number;
+ count?: number | undefined;
};
/** @internal */
@@ -3701,6 +3704,7 @@ export const CreatePlanPurchaseLimitRequest$outboundSchema: z.ZodMiniType<
interval: CreatePlanPurchaseLimitIntervalRequestBody$outboundSchema,
intervalCount: z._default(z.number(), 1),
limit: z.number(),
+ count: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
@@ -5104,6 +5108,7 @@ export const CreatePlanVariantDetailsPurchaseLimit$inboundSchema: z.ZodMiniType<
interval: CreatePlanVariantDetailsPurchaseLimitInterval$inboundSchema,
interval_count: z._default(types.number(), 1),
limit: types.number(),
+ count: types.optional(types.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/create-schedule-op.ts b/packages/sdk/src/models/create-schedule-op.ts
index 60019d0e99..ed42c4b86b 100644
--- a/packages/sdk/src/models/create-schedule-op.ts
+++ b/packages/sdk/src/models/create-schedule-op.ts
@@ -767,7 +767,7 @@ export type CreateSchedulePurchaseLimitInterval2 = ClosedEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type CreateSchedulePurchaseLimit2 = {
/**
@@ -782,6 +782,10 @@ export type CreateSchedulePurchaseLimit2 = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type CreateScheduleAutoTopup2 = {
@@ -802,7 +806,7 @@ export type CreateScheduleAutoTopup2 = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: CreateSchedulePurchaseLimit2 | undefined;
/**
@@ -2140,6 +2144,7 @@ export type CreateSchedulePurchaseLimit2$Outbound = {
interval: string;
interval_count: number;
limit: number;
+ count?: number | undefined;
};
/** @internal */
@@ -2151,6 +2156,7 @@ export const CreateSchedulePurchaseLimit2$outboundSchema: z.ZodMiniType<
interval: CreateSchedulePurchaseLimitInterval2$outboundSchema,
intervalCount: z._default(z.number(), 1),
limit: z.number(),
+ count: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/customer-data.ts b/packages/sdk/src/models/customer-data.ts
index d7b53b00bc..85e02d59cd 100644
--- a/packages/sdk/src/models/customer-data.ts
+++ b/packages/sdk/src/models/customer-data.ts
@@ -24,7 +24,7 @@ export type CustomerDataPurchaseLimitInterval = ClosedEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type CustomerDataPurchaseLimit = {
/**
@@ -39,6 +39,10 @@ export type CustomerDataPurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type CustomerDataAutoTopup = {
@@ -59,7 +63,7 @@ export type CustomerDataAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: CustomerDataPurchaseLimit | undefined;
/**
@@ -301,6 +305,7 @@ export type CustomerDataPurchaseLimit$Outbound = {
interval: string;
interval_count: number;
limit: number;
+ count?: number | undefined;
};
/** @internal */
@@ -312,6 +317,7 @@ export const CustomerDataPurchaseLimit$outboundSchema: z.ZodMiniType<
interval: CustomerDataPurchaseLimitInterval$outboundSchema,
intervalCount: z._default(z.number(), 1),
limit: z.number(),
+ count: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
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 8bd7e9a076..de248bb322 100644
--- a/packages/sdk/src/models/get-or-create-customer-op.ts
+++ b/packages/sdk/src/models/get-or-create-customer-op.ts
@@ -32,7 +32,7 @@ export type GetOrCreateCustomerPurchaseLimitInterval = ClosedEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type GetOrCreateCustomerPurchaseLimit = {
/**
@@ -47,6 +47,10 @@ export type GetOrCreateCustomerPurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type GetOrCreateCustomerAutoTopup = {
@@ -67,7 +71,7 @@ export type GetOrCreateCustomerAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: GetOrCreateCustomerPurchaseLimit | undefined;
/**
@@ -314,6 +318,7 @@ export type GetOrCreateCustomerPurchaseLimit$Outbound = {
interval: string;
interval_count: number;
limit: number;
+ count?: number | undefined;
};
/** @internal */
@@ -325,6 +330,7 @@ export const GetOrCreateCustomerPurchaseLimit$outboundSchema: z.ZodMiniType<
interval: GetOrCreateCustomerPurchaseLimitInterval$outboundSchema,
intervalCount: z._default(z.number(), 1),
limit: z.number(),
+ count: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/get-plan-op.ts b/packages/sdk/src/models/get-plan-op.ts
index deed3d3a32..bf5e1279f3 100644
--- a/packages/sdk/src/models/get-plan-op.ts
+++ b/packages/sdk/src/models/get-plan-op.ts
@@ -896,7 +896,7 @@ export type GetPlanVariantDetailsPurchaseLimitInterval = OpenEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type GetPlanVariantDetailsPurchaseLimit = {
/**
@@ -911,6 +911,10 @@ export type GetPlanVariantDetailsPurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type GetPlanVariantDetailsAutoTopup = {
@@ -931,7 +935,7 @@ export type GetPlanVariantDetailsAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: GetPlanVariantDetailsPurchaseLimit | undefined;
/**
@@ -2503,6 +2507,7 @@ export const GetPlanVariantDetailsPurchaseLimit$inboundSchema: z.ZodMiniType<
interval: GetPlanVariantDetailsPurchaseLimitInterval$inboundSchema,
interval_count: z._default(types.number(), 1),
limit: types.number(),
+ count: types.optional(types.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/index.ts b/packages/sdk/src/models/index.ts
index 108897300b..c38815d4e2 100644
--- a/packages/sdk/src/models/index.ts
+++ b/packages/sdk/src/models/index.ts
@@ -34,6 +34,7 @@ export * from "./get-plan-op.js";
export * from "./get-revenue-cat-keys-op.js";
export * from "./http-client-errors.js";
export * from "./import-op.js";
+export * from "./insert-invoices-op.js";
export * from "./link-revenue-cat-op.js";
export * from "./list-customers-op.js";
export * from "./list-entities-op.js";
diff --git a/packages/sdk/src/models/insert-invoices-op.ts b/packages/sdk/src/models/insert-invoices-op.ts
new file mode 100644
index 0000000000..b48d34e3cc
--- /dev/null
+++ b/packages/sdk/src/models/insert-invoices-op.ts
@@ -0,0 +1,333 @@
+/*
+ * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.
+ */
+
+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 { ClosedEnum, OpenEnum } from "../types/enums.js";
+import { Result as SafeParseResult } from "../types/fp.js";
+import * as types from "../types/primitives.js";
+import { SDKValidationError } from "./sdk-validation-error.js";
+
+export type InsertInvoicesGlobals = {
+ xApiVersion?: string | undefined;
+};
+
+/**
+ * The billing processor that owns this invoice.
+ */
+export const InsertInvoicesProcessorTypeRequest = {
+ Stripe: "stripe",
+ Revenuecat: "revenuecat",
+} as const;
+/**
+ * The billing processor that owns this invoice.
+ */
+export type InsertInvoicesProcessorTypeRequest = ClosedEnum<
+ typeof InsertInvoicesProcessorTypeRequest
+>;
+
+/**
+ * The invoice status.
+ */
+export const InsertInvoicesStatus = {
+ Draft: "draft",
+ Open: "open",
+ Void: "void",
+ Paid: "paid",
+ Uncollectible: "uncollectible",
+} as const;
+/**
+ * The invoice status.
+ */
+export type InsertInvoicesStatus = ClosedEnum;
+
+export type InvoiceRequestBody = {
+ /**
+ * The customer this invoice belongs to.
+ */
+ customerId: string;
+ /**
+ * Plan IDs represented by this invoice.
+ */
+ planIds?: Array | undefined;
+ /**
+ * The processor's stable invoice ID.
+ */
+ stripeId: string;
+ /**
+ * The billing processor that owns this invoice.
+ */
+ processorType?: InsertInvoicesProcessorTypeRequest | undefined;
+ /**
+ * The invoice status.
+ */
+ status: InsertInvoicesStatus;
+ /**
+ * The invoice total in major currency units.
+ */
+ total: number;
+ /**
+ * The amount paid in major currency units.
+ */
+ amountPaid?: number | null | undefined;
+ /**
+ * The refunded amount in major currency units.
+ */
+ refundedAmount?: number | undefined;
+ /**
+ * The currency code. Defaults to the organization's default currency.
+ */
+ currency?: string | undefined;
+ /**
+ * The invoice creation timestamp in milliseconds.
+ */
+ createdAt: number;
+ /**
+ * The hosted invoice URL, when available.
+ */
+ hostedInvoiceUrl?: string | null | undefined;
+};
+
+export type InsertInvoicesParams = {
+ /**
+ * Invoices to insert or update, in response order.
+ */
+ invoices: Array;
+};
+
+/**
+ * The billing processor that owns this invoice.
+ */
+export const InsertInvoicesProcessorTypeResponse = {
+ Stripe: "stripe",
+ Revenuecat: "revenuecat",
+} as const;
+/**
+ * The billing processor that owns this invoice.
+ */
+export type InsertInvoicesProcessorTypeResponse = OpenEnum<
+ typeof InsertInvoicesProcessorTypeResponse
+>;
+
+export type InsertInvoicesInvoiceResponse = {
+ /**
+ * Array of plan IDs included in this invoice
+ */
+ planIds: Array;
+ /**
+ * The Stripe invoice ID
+ */
+ stripeId: string;
+ /**
+ * The billing processor that owns this invoice.
+ */
+ processorType: InsertInvoicesProcessorTypeResponse;
+ /**
+ * The status of the invoice
+ */
+ status: string;
+ /**
+ * The total amount of the invoice
+ */
+ total: number;
+ /**
+ * The currency code for the invoice
+ */
+ currency: string;
+ /**
+ * Timestamp when the invoice was created
+ */
+ createdAt: number;
+ /**
+ * URL to the Stripe-hosted invoice page
+ */
+ hostedInvoiceUrl?: string | null | undefined;
+ /**
+ * The Autumn invoice ID.
+ */
+ id: string;
+ /**
+ * The customer this invoice belongs to.
+ */
+ customerId: string;
+ /**
+ * The amount paid in major currency units.
+ */
+ amountPaid: number | null;
+ /**
+ * The refunded amount in major currency units.
+ */
+ refundedAmount: number;
+};
+
+/**
+ * OK
+ */
+export type InsertInvoicesResponse = {
+ /**
+ * Inserted or updated invoices in request order.
+ */
+ invoices: Array;
+};
+
+/** @internal */
+export const InsertInvoicesProcessorTypeRequest$outboundSchema: z.ZodMiniEnum<
+ typeof InsertInvoicesProcessorTypeRequest
+> = z.enum(InsertInvoicesProcessorTypeRequest);
+
+/** @internal */
+export const InsertInvoicesStatus$outboundSchema: z.ZodMiniEnum<
+ typeof InsertInvoicesStatus
+> = z.enum(InsertInvoicesStatus);
+
+/** @internal */
+export type InvoiceRequestBody$Outbound = {
+ customer_id: string;
+ plan_ids?: Array | undefined;
+ stripe_id: string;
+ processor_type: string;
+ status: string;
+ total: number;
+ amount_paid?: number | null | undefined;
+ refunded_amount: number;
+ currency?: string | undefined;
+ created_at: number;
+ hosted_invoice_url?: string | null | undefined;
+};
+
+/** @internal */
+export const InvoiceRequestBody$outboundSchema: z.ZodMiniType<
+ InvoiceRequestBody$Outbound,
+ InvoiceRequestBody
+> = z.pipe(
+ z.object({
+ customerId: z.string(),
+ planIds: z.optional(z.array(z.string())),
+ stripeId: z.string(),
+ processorType: z._default(
+ InsertInvoicesProcessorTypeRequest$outboundSchema,
+ "stripe",
+ ),
+ status: InsertInvoicesStatus$outboundSchema,
+ total: z.number(),
+ amountPaid: z.optional(z.nullable(z.number())),
+ refundedAmount: z._default(z.number(), 0),
+ currency: z.optional(z.string()),
+ createdAt: z.number(),
+ hostedInvoiceUrl: z.optional(z.nullable(z.string())),
+ }),
+ z.transform((v) => {
+ return remap$(v, {
+ customerId: "customer_id",
+ planIds: "plan_ids",
+ stripeId: "stripe_id",
+ processorType: "processor_type",
+ amountPaid: "amount_paid",
+ refundedAmount: "refunded_amount",
+ createdAt: "created_at",
+ hostedInvoiceUrl: "hosted_invoice_url",
+ });
+ }),
+);
+
+export function invoiceRequestBodyToJSON(
+ invoiceRequestBody: InvoiceRequestBody,
+): string {
+ return JSON.stringify(
+ InvoiceRequestBody$outboundSchema.parse(invoiceRequestBody),
+ );
+}
+
+/** @internal */
+export type InsertInvoicesParams$Outbound = {
+ invoices: Array;
+};
+
+/** @internal */
+export const InsertInvoicesParams$outboundSchema: z.ZodMiniType<
+ InsertInvoicesParams$Outbound,
+ InsertInvoicesParams
+> = z.object({
+ invoices: z.array(z.lazy(() => InvoiceRequestBody$outboundSchema)),
+});
+
+export function insertInvoicesParamsToJSON(
+ insertInvoicesParams: InsertInvoicesParams,
+): string {
+ return JSON.stringify(
+ InsertInvoicesParams$outboundSchema.parse(insertInvoicesParams),
+ );
+}
+
+/** @internal */
+export const InsertInvoicesProcessorTypeResponse$inboundSchema: z.ZodMiniType<
+ InsertInvoicesProcessorTypeResponse,
+ unknown
+> = openEnums.inboundSchema(InsertInvoicesProcessorTypeResponse);
+
+/** @internal */
+export const InsertInvoicesInvoiceResponse$inboundSchema: z.ZodMiniType<
+ InsertInvoicesInvoiceResponse,
+ unknown
+> = z.pipe(
+ z.object({
+ plan_ids: z.array(types.string()),
+ stripe_id: types.string(),
+ processor_type: z._default(
+ InsertInvoicesProcessorTypeResponse$inboundSchema,
+ "stripe",
+ ),
+ status: types.string(),
+ total: types.number(),
+ currency: types.string(),
+ created_at: types.number(),
+ hosted_invoice_url: z.optional(z.nullable(types.string())),
+ id: types.string(),
+ customer_id: types.string(),
+ amount_paid: types.nullable(types.number()),
+ refunded_amount: types.number(),
+ }),
+ z.transform((v) => {
+ return remap$(v, {
+ "plan_ids": "planIds",
+ "stripe_id": "stripeId",
+ "processor_type": "processorType",
+ "created_at": "createdAt",
+ "hosted_invoice_url": "hostedInvoiceUrl",
+ "customer_id": "customerId",
+ "amount_paid": "amountPaid",
+ "refunded_amount": "refundedAmount",
+ });
+ }),
+);
+
+export function insertInvoicesInvoiceResponseFromJSON(
+ jsonString: string,
+): SafeParseResult {
+ return safeParse(
+ jsonString,
+ (x) => InsertInvoicesInvoiceResponse$inboundSchema.parse(JSON.parse(x)),
+ `Failed to parse 'InsertInvoicesInvoiceResponse' from JSON`,
+ );
+}
+
+/** @internal */
+export const InsertInvoicesResponse$inboundSchema: z.ZodMiniType<
+ InsertInvoicesResponse,
+ unknown
+> = z.object({
+ invoices: z.array(z.lazy(() => InsertInvoicesInvoiceResponse$inboundSchema)),
+});
+
+export function insertInvoicesResponseFromJSON(
+ jsonString: string,
+): SafeParseResult {
+ return safeParse(
+ jsonString,
+ (x) => InsertInvoicesResponse$inboundSchema.parse(JSON.parse(x)),
+ `Failed to parse 'InsertInvoicesResponse' from JSON`,
+ );
+}
diff --git a/packages/sdk/src/models/list-invoices-op.ts b/packages/sdk/src/models/list-invoices-op.ts
index f071ebd0f9..006dc30d4c 100644
--- a/packages/sdk/src/models/list-invoices-op.ts
+++ b/packages/sdk/src/models/list-invoices-op.ts
@@ -24,11 +24,13 @@ export const ListInvoicesStatus = {
} as const;
export type ListInvoicesStatus = ClosedEnum;
-export const ProcessorTypeRequest = {
+export const ListInvoicesProcessorTypeRequest = {
Stripe: "stripe",
Revenuecat: "revenuecat",
} as const;
-export type ProcessorTypeRequest = ClosedEnum;
+export type ListInvoicesProcessorTypeRequest = ClosedEnum<
+ typeof ListInvoicesProcessorTypeRequest
+>;
export type ListInvoicesParams = {
/**
@@ -54,7 +56,7 @@ export type ListInvoicesParams = {
/**
* Filter by billing processor (stripe, revenuecat). Invoices recorded before processor tracking count as stripe.
*/
- processorTypes?: Array | undefined;
+ processorTypes?: Array | undefined;
};
/**
@@ -146,9 +148,9 @@ export const ListInvoicesStatus$outboundSchema: z.ZodMiniEnum<
> = z.enum(ListInvoicesStatus);
/** @internal */
-export const ProcessorTypeRequest$outboundSchema: z.ZodMiniEnum<
- typeof ProcessorTypeRequest
-> = z.enum(ProcessorTypeRequest);
+export const ListInvoicesProcessorTypeRequest$outboundSchema: z.ZodMiniEnum<
+ typeof ListInvoicesProcessorTypeRequest
+> = z.enum(ListInvoicesProcessorTypeRequest);
/** @internal */
export type ListInvoicesParams$Outbound = {
@@ -171,7 +173,9 @@ export const ListInvoicesParams$outboundSchema: z.ZodMiniType<
customerId: z.optional(z.string()),
entityId: z.optional(z.string()),
status: z.optional(z.array(ListInvoicesStatus$outboundSchema)),
- processorTypes: z.optional(z.array(ProcessorTypeRequest$outboundSchema)),
+ processorTypes: z.optional(
+ z.array(ListInvoicesProcessorTypeRequest$outboundSchema),
+ ),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/list-plans-op.ts b/packages/sdk/src/models/list-plans-op.ts
index 78221e2253..41c54675da 100644
--- a/packages/sdk/src/models/list-plans-op.ts
+++ b/packages/sdk/src/models/list-plans-op.ts
@@ -887,7 +887,7 @@ export type ListPlansVariantDetailsPurchaseLimitInterval = OpenEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type ListPlansVariantDetailsPurchaseLimit = {
/**
@@ -902,6 +902,10 @@ export type ListPlansVariantDetailsPurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type ListPlansVariantDetailsAutoTopup = {
@@ -922,7 +926,7 @@ export type ListPlansVariantDetailsAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: ListPlansVariantDetailsPurchaseLimit | undefined;
/**
@@ -2471,6 +2475,7 @@ export const ListPlansVariantDetailsPurchaseLimit$inboundSchema: z.ZodMiniType<
interval: ListPlansVariantDetailsPurchaseLimitInterval$inboundSchema,
interval_count: z._default(types.number(), 1),
limit: types.number(),
+ count: types.optional(types.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/plan.ts b/packages/sdk/src/models/plan.ts
index 861af25ded..178af66132 100644
--- a/packages/sdk/src/models/plan.ts
+++ b/packages/sdk/src/models/plan.ts
@@ -869,7 +869,7 @@ export type PlanVariantDetailsPurchaseLimitInterval = OpenEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type PlanVariantDetailsPurchaseLimit = {
/**
@@ -884,6 +884,10 @@ export type PlanVariantDetailsPurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type PlanVariantDetailsAutoTopup = {
@@ -904,7 +908,7 @@ export type PlanVariantDetailsAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: PlanVariantDetailsPurchaseLimit | undefined;
/**
@@ -2411,6 +2415,7 @@ export const PlanVariantDetailsPurchaseLimit$inboundSchema: z.ZodMiniType<
interval: PlanVariantDetailsPurchaseLimitInterval$inboundSchema,
interval_count: z._default(types.number(), 1),
limit: types.number(),
+ count: types.optional(types.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/preview-attach-op.ts b/packages/sdk/src/models/preview-attach-op.ts
index 23b0f12466..b395a92c85 100644
--- a/packages/sdk/src/models/preview-attach-op.ts
+++ b/packages/sdk/src/models/preview-attach-op.ts
@@ -769,7 +769,7 @@ export type PreviewAttachPurchaseLimitInterval = ClosedEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type PreviewAttachPurchaseLimit = {
/**
@@ -784,6 +784,10 @@ export type PreviewAttachPurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type PreviewAttachAutoTopup = {
@@ -804,7 +808,7 @@ export type PreviewAttachAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: PreviewAttachPurchaseLimit | undefined;
/**
@@ -2963,6 +2967,7 @@ export type PreviewAttachPurchaseLimit$Outbound = {
interval: string;
interval_count: number;
limit: number;
+ count?: number | undefined;
};
/** @internal */
@@ -2974,6 +2979,7 @@ export const PreviewAttachPurchaseLimit$outboundSchema: z.ZodMiniType<
interval: PreviewAttachPurchaseLimitInterval$outboundSchema,
intervalCount: z._default(z.number(), 1),
limit: z.number(),
+ count: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/preview-update-op.ts b/packages/sdk/src/models/preview-update-op.ts
index 6e2a2d1cfb..2ddf0ba99e 100644
--- a/packages/sdk/src/models/preview-update-op.ts
+++ b/packages/sdk/src/models/preview-update-op.ts
@@ -769,7 +769,7 @@ export type PreviewUpdatePurchaseLimitInterval = ClosedEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type PreviewUpdatePurchaseLimit = {
/**
@@ -784,6 +784,10 @@ export type PreviewUpdatePurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type PreviewUpdateAutoTopup = {
@@ -804,7 +808,7 @@ export type PreviewUpdateAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: PreviewUpdatePurchaseLimit | undefined;
/**
@@ -2917,6 +2921,7 @@ export type PreviewUpdatePurchaseLimit$Outbound = {
interval: string;
interval_count: number;
limit: number;
+ count?: number | undefined;
};
/** @internal */
@@ -2928,6 +2933,7 @@ export const PreviewUpdatePurchaseLimit$outboundSchema: z.ZodMiniType<
interval: PreviewUpdatePurchaseLimitInterval$outboundSchema,
intervalCount: z._default(z.number(), 1),
limit: z.number(),
+ count: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/setup-payment-op.ts b/packages/sdk/src/models/setup-payment-op.ts
index d806443282..65bab2b342 100644
--- a/packages/sdk/src/models/setup-payment-op.ts
+++ b/packages/sdk/src/models/setup-payment-op.ts
@@ -767,7 +767,7 @@ export type SetupPaymentPurchaseLimitInterval = ClosedEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type SetupPaymentPurchaseLimit = {
/**
@@ -782,6 +782,10 @@ export type SetupPaymentPurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type SetupPaymentAutoTopup = {
@@ -802,7 +806,7 @@ export type SetupPaymentAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: SetupPaymentPurchaseLimit | undefined;
/**
@@ -2537,6 +2541,7 @@ export type SetupPaymentPurchaseLimit$Outbound = {
interval: string;
interval_count: number;
limit: number;
+ count?: number | undefined;
};
/** @internal */
@@ -2548,6 +2553,7 @@ export const SetupPaymentPurchaseLimit$outboundSchema: z.ZodMiniType<
interval: SetupPaymentPurchaseLimitInterval$outboundSchema,
intervalCount: z._default(z.number(), 1),
limit: z.number(),
+ count: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/update-customer-op.ts b/packages/sdk/src/models/update-customer-op.ts
index 98437d4b30..2ea2f18148 100644
--- a/packages/sdk/src/models/update-customer-op.ts
+++ b/packages/sdk/src/models/update-customer-op.ts
@@ -35,7 +35,7 @@ export type UpdateCustomerPurchaseLimitIntervalRequestBody = ClosedEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type UpdateCustomerPurchaseLimitRequest = {
/**
@@ -50,6 +50,10 @@ export type UpdateCustomerPurchaseLimitRequest = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type UpdateCustomerAutoTopupRequest = {
@@ -70,7 +74,7 @@ export type UpdateCustomerAutoTopupRequest = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: UpdateCustomerPurchaseLimitRequest | undefined;
/**
@@ -1098,6 +1102,7 @@ export type UpdateCustomerPurchaseLimitRequest$Outbound = {
interval: string;
interval_count: number;
limit: number;
+ count?: number | undefined;
};
/** @internal */
@@ -1109,6 +1114,7 @@ export const UpdateCustomerPurchaseLimitRequest$outboundSchema: z.ZodMiniType<
interval: UpdateCustomerPurchaseLimitIntervalRequestBody$outboundSchema,
intervalCount: z._default(z.number(), 1),
limit: z.number(),
+ count: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/models/update-plan-op.ts b/packages/sdk/src/models/update-plan-op.ts
index 0f847edb90..6f2a857f17 100644
--- a/packages/sdk/src/models/update-plan-op.ts
+++ b/packages/sdk/src/models/update-plan-op.ts
@@ -742,10 +742,6 @@ export type UpdatePlanLicense = {
prepaidOnly?: boolean | undefined;
customize?: UpdatePlanLicenseCustomize | null | undefined;
metadata?: { [k: string]: any } | undefined;
- /**
- * Pin the link to a specific version of the license plan. Omitted, an existing link keeps its pinned version and a new link resolves to the latest.
- */
- version?: number | undefined;
};
/**
@@ -824,7 +820,7 @@ export type UpdatePlanPurchaseLimitIntervalRequestBody = ClosedEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type UpdatePlanPurchaseLimitRequest = {
/**
@@ -839,6 +835,10 @@ export type UpdatePlanPurchaseLimitRequest = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type UpdatePlanAutoTopupRequest = {
@@ -859,7 +859,7 @@ export type UpdatePlanAutoTopupRequest = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: UpdatePlanPurchaseLimitRequest | undefined;
/**
@@ -1034,6 +1034,11 @@ export type Migration = {
includeCustom?: boolean | undefined;
};
+export type UpdateLicenseParent = {
+ planId: string;
+ version: number;
+};
+
/**
* Billing interval (e.g. 'month', 'year').
*/
@@ -1472,7 +1477,7 @@ export type VariantPurchaseLimitInterval = ClosedEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type VariantPurchaseLimit = {
/**
@@ -1487,6 +1492,10 @@ export type VariantPurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type VariantAutoTopup = {
@@ -1507,7 +1516,7 @@ export type VariantAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: VariantPurchaseLimit | undefined;
/**
@@ -1806,6 +1815,10 @@ export type UpdatePlanParams = {
* Variant plan IDs to apply this update to. Empty or omitted means no propagation.
*/
updateVariantIds?: Array | undefined;
+ /**
+ * Parent plan versions that should receive this license-plan update.
+ */
+ updateLicenseParents?: Array | undefined;
/**
* Additive variant updates for this base plan. Missing variants are created when name is provided.
*/
@@ -2703,7 +2716,7 @@ export type UpdatePlanVariantDetailsPurchaseLimitInterval = OpenEnum<
>;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
export type UpdatePlanVariantDetailsPurchaseLimit = {
/**
@@ -2718,6 +2731,10 @@ export type UpdatePlanVariantDetailsPurchaseLimit = {
* Maximum number of auto top-ups allowed within the interval.
*/
limit: number;
+ /**
+ * Set the current window's consumed auto top-up count. Omit to leave runtime state unchanged.
+ */
+ count?: number | undefined;
};
export type UpdatePlanVariantDetailsAutoTopup = {
@@ -2738,7 +2755,7 @@ export type UpdatePlanVariantDetailsAutoTopup = {
*/
quantity: number;
/**
- * Optional rate limit to cap how often auto top-ups occur.
+ * Optional rate limit to cap how often auto top-ups occur. Pass count to set the current window's consumed top-ups.
*/
purchaseLimit?: UpdatePlanVariantDetailsPurchaseLimit | undefined;
/**
@@ -4298,7 +4315,6 @@ export type UpdatePlanLicense$Outbound = {
prepaid_only?: boolean | undefined;
customize?: UpdatePlanLicenseCustomize$Outbound | null | undefined;
metadata?: { [k: string]: any } | undefined;
- version?: number | undefined;
};
/** @internal */
@@ -4314,7 +4330,6 @@ export const UpdatePlanLicense$outboundSchema: z.ZodMiniType<
z.nullable(z.lazy(() => UpdatePlanLicenseCustomize$outboundSchema)),
),
metadata: z.optional(z.record(z.string(), z.any())),
- version: z.optional(z.int()),
}),
z.transform((v) => {
return remap$(v, {
@@ -4423,6 +4438,7 @@ export type UpdatePlanPurchaseLimitRequest$Outbound = {
interval: string;
interval_count: number;
limit: number;
+ count?: number | undefined;
};
/** @internal */
@@ -4434,6 +4450,7 @@ export const UpdatePlanPurchaseLimitRequest$outboundSchema: z.ZodMiniType<
interval: UpdatePlanPurchaseLimitIntervalRequestBody$outboundSchema,
intervalCount: z._default(z.number(), 1),
limit: z.number(),
+ count: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
@@ -4779,6 +4796,36 @@ export function migrationToJSON(migration: Migration): string {
return JSON.stringify(Migration$outboundSchema.parse(migration));
}
+/** @internal */
+export type UpdateLicenseParent$Outbound = {
+ plan_id: string;
+ version: number;
+};
+
+/** @internal */
+export const UpdateLicenseParent$outboundSchema: z.ZodMiniType<
+ UpdateLicenseParent$Outbound,
+ UpdateLicenseParent
+> = z.pipe(
+ z.object({
+ planId: z.string(),
+ version: z.int(),
+ }),
+ z.transform((v) => {
+ return remap$(v, {
+ planId: "plan_id",
+ });
+ }),
+);
+
+export function updateLicenseParentToJSON(
+ updateLicenseParent: UpdateLicenseParent,
+): string {
+ return JSON.stringify(
+ UpdateLicenseParent$outboundSchema.parse(updateLicenseParent),
+ );
+}
+
/** @internal */
export const PriceVariantInterval$outboundSchema: z.ZodMiniEnum<
typeof PriceVariantInterval
@@ -5296,6 +5343,7 @@ export type VariantPurchaseLimit$Outbound = {
interval: string;
interval_count: number;
limit: number;
+ count?: number | undefined;
};
/** @internal */
@@ -5307,6 +5355,7 @@ export const VariantPurchaseLimit$outboundSchema: z.ZodMiniType<
interval: VariantPurchaseLimitInterval$outboundSchema,
intervalCount: z._default(z.number(), 1),
limit: z.number(),
+ count: z.optional(z.number()),
}),
z.transform((v) => {
return remap$(v, {
@@ -5745,6 +5794,7 @@ export type UpdatePlanParams$Outbound = {
migration?: Migration$Outbound | undefined;
force_version?: boolean | undefined;
update_variant_ids?: Array | undefined;
+ update_license_parents?: Array | undefined;
variants?: Array | undefined;
is_default?: boolean | undefined;
};
@@ -5788,6 +5838,9 @@ export const UpdatePlanParams$outboundSchema: z.ZodMiniType<
migration: z.optional(z.lazy(() => Migration$outboundSchema)),
forceVersion: z.optional(z.boolean()),
updateVariantIds: z.optional(z.array(z.string())),
+ updateLicenseParents: z.optional(
+ z.array(z.lazy(() => UpdateLicenseParent$outboundSchema)),
+ ),
variants: z.optional(z.array(z.lazy(() => Variant$outboundSchema))),
isDefault: z.optional(z.boolean()),
}),
@@ -5805,6 +5858,7 @@ export const UpdatePlanParams$outboundSchema: z.ZodMiniType<
allVersions: "all_versions",
forceVersion: "force_version",
updateVariantIds: "update_variant_ids",
+ updateLicenseParents: "update_license_parents",
isDefault: "is_default",
});
}),
@@ -6837,6 +6891,7 @@ export const UpdatePlanVariantDetailsPurchaseLimit$inboundSchema: z.ZodMiniType<
interval: UpdatePlanVariantDetailsPurchaseLimitInterval$inboundSchema,
interval_count: z._default(types.number(), 1),
limit: types.number(),
+ count: types.optional(types.number()),
}),
z.transform((v) => {
return remap$(v, {
diff --git a/packages/sdk/src/sdk/billing.ts b/packages/sdk/src/sdk/billing.ts
index ded8bbc8c8..8bf3abf77f 100644
--- a/packages/sdk/src/sdk/billing.ts
+++ b/packages/sdk/src/sdk/billing.ts
@@ -93,7 +93,7 @@ export class Billing extends ClientSDK {
* @example
* ```typescript
* // Schedule a transition from a trial plan to a paid plan
- * const response = await client.billing.createSchedule({ customerId: "cus_123", phases: [{"startsAt":1784299196316,"plans":[{"planId":"trial_plan"}]},{"startsAt":1785508796316,"plans":[{"planId":"pro_plan"}]}] });
+ * const response = await client.billing.createSchedule({ customerId: "cus_123", phases: [{"startsAt":1784744849888,"plans":[{"planId":"trial_plan"}]},{"startsAt":1785954449888,"plans":[{"planId":"pro_plan"}]}] });
* ```
*
* @param customerId - The ID of the customer to create the schedule for.
diff --git a/packages/sdk/src/sdk/invoices.ts b/packages/sdk/src/sdk/invoices.ts
index 76d65ab90a..2bc21cdbfb 100644
--- a/packages/sdk/src/sdk/invoices.ts
+++ b/packages/sdk/src/sdk/invoices.ts
@@ -2,12 +2,27 @@
* Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT.
*/
+import { invoicesInsert } from "../funcs/invoices-insert.js";
import { invoicesList } from "../funcs/invoices-list.js";
import { ClientSDK, RequestOptions } from "../lib/sdks.js";
import * as models from "../models/index.js";
import { unwrapAsync } from "../types/fp.js";
export class Invoices extends ClientSDK {
+ /**
+ * Inserts or updates up to 500 historical invoices without reading or mutating the billing processor.
+ */
+ async insert(
+ request: models.InsertInvoicesParams,
+ options?: RequestOptions,
+ ): Promise {
+ return unwrapAsync(invoicesInsert(
+ this,
+ request,
+ options,
+ ));
+ }
+
/**
* Lists invoices with cursor pagination and optional filters (customer, entity, status, processor). Pass `start_cursor: ""` (or omit) for the first page; use `next_cursor` from a prior response for subsequent pages.
*/
diff --git a/packages/sdk/src/sdk/plans.ts b/packages/sdk/src/sdk/plans.ts
index b9f2d8052b..2c0a568fa7 100644
--- a/packages/sdk/src/sdk/plans.ts
+++ b/packages/sdk/src/sdk/plans.ts
@@ -217,6 +217,7 @@ export class Plans extends ClientSDK {
* @param allVersions - Apply the update diff to all versions of this plan. Mutually exclusive with disable_version. (optional)
* @param forceVersion - Force versioning even when no customers exist. Mutually exclusive with disable_version. (optional)
* @param updateVariantIds - Variant plan IDs to apply this update to. Empty or omitted means no propagation. (optional)
+ * @param updateLicenseParents - Parent plan versions that should receive this license-plan update. (optional)
* @param variants - Additive variant updates for this base plan. Missing variants are created when name is provided. (optional)
* @param isDefault - Whether this is the org's default plan. Cannot be true on a variant. (optional)
*
diff --git a/scripts/tw/helpers/preflight.ts b/scripts/tw/helpers/preflight.ts
index 33229e9288..8b57d1db35 100644
--- a/scripts/tw/helpers/preflight.ts
+++ b/scripts/tw/helpers/preflight.ts
@@ -62,7 +62,7 @@ const checkSecrets = (): PreflightProblem | undefined => {
};
const checkGit = (ref: string): PreflightProblem | undefined => {
- const dirty = git("status", "--porcelain");
+ const dirty = git("status", "--porcelain", "--", ".", ":(exclude)ai");
if (dirty) {
return {
what: "uncommitted changes — workers clone origin/[, so they won't see them",
diff --git a/server/experiments/explainResetContext.ts b/server/experiments/explainResetContext.ts
new file mode 100644
index 0000000000..7ac6e246b6
--- /dev/null
+++ b/server/experiments/explainResetContext.ts
@@ -0,0 +1,78 @@
+import "dotenv/config";
+import { sql } from "drizzle-orm";
+import { loadLocalEnv } from "../src/utils/envUtils";
+
+loadLocalEnv();
+
+const { initDrizzle } = await import("../src/db/initDrizzle");
+const { getResetEligibleCustomerEntitlementsPage } = await import(
+ "../src/internal/customers/cusProducts/cusEnts/repos/getResetEligibleCustomerEntitlementsPage"
+);
+const { buildResetContextByIdsQuery, getResetContextByIds } = await import(
+ "../src/internal/customers/cusProducts/cusEnts/repos/getResetContextByIds"
+);
+
+// Benchmarks the batch reset worker's ID hydration: scans one keyset page,
+// then times and explains the same ID-based query the worker runs.
+//
+// Run with:
+// bun run experiments/explainResetContext.ts
+// Options (env vars):
+// RESET_CONTEXT_BATCH_SIZE=1000 number of cusEnt IDs to hydrate
+
+const main = async () => {
+ const batchSize = Number(process.env.RESET_CONTEXT_BATCH_SIZE ?? 1000);
+ const { db } = initDrizzle();
+ const dueBefore = Date.now();
+
+ const page = await getResetEligibleCustomerEntitlementsPage({
+ db,
+ dueBefore,
+ cursor: null,
+ limit: batchSize,
+ });
+ const customerEntitlementIds = page.map((row) => row.id);
+ if (customerEntitlementIds.length === 0) {
+ console.log("Scanned 0 eligible customer entitlements");
+ process.exit(0);
+ }
+ console.log(`Scanned ${page.length} eligible customer entitlement IDs\n`);
+
+ // Full repo call, cold then warm.
+ for (const run of ["cold", "warm"]) {
+ const start = performance.now();
+ const result = await getResetContextByIds({
+ db,
+ customerEntitlementIds,
+ });
+ const elapsed = performance.now() - start;
+ console.log(
+ `[hydrate][${run}] rows=${result.customerEntitlements.length}, missing=${result.missingIds.length}, wall-clock=${elapsed.toFixed(0)}ms`,
+ );
+ }
+
+ // Query-only timing, to separate row-mapping cost from DB cost.
+ {
+ const start = performance.now();
+ const rows = await db.execute(
+ buildResetContextByIdsQuery({ customerEntitlementIds }),
+ );
+ const elapsed = performance.now() - start;
+ console.log(
+ `[query-only] rows=${rows.length}, wall-clock=${elapsed.toFixed(0)}ms\n`,
+ );
+ }
+
+ console.log("--- EXPLAIN (ANALYZE, BUFFERS) ---");
+ const explainResult = await db.execute(
+ sql`EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) ${buildResetContextByIdsQuery({ customerEntitlementIds })}`,
+ );
+ for (const row of explainResult) {
+ const line = (row as Record)["QUERY PLAN"];
+ if (typeof line === "string") console.log(line);
+ }
+
+ process.exit(0);
+};
+
+await main();
diff --git a/server/experiments/explainResetCount.ts b/server/experiments/explainResetCount.ts
new file mode 100644
index 0000000000..4030f0d1bd
--- /dev/null
+++ b/server/experiments/explainResetCount.ts
@@ -0,0 +1,58 @@
+import "dotenv/config";
+import { sql } from "drizzle-orm";
+import { loadLocalEnv } from "../src/utils/envUtils";
+
+loadLocalEnv();
+
+const { initDrizzle } = await import("../src/db/initDrizzle");
+const { buildCountResetEligibleQuery, countResetEligibleCustomerEntitlements } =
+ await import(
+ "../src/internal/customers/cusProducts/cusEnts/repos/countResetEligibleCustomerEntitlements"
+ );
+
+// Benchmarks the backlog-gauge count (countResetEligibleCustomerEntitlements)
+// that the V2 reset cron logs every few minutes — verifies the capped count
+// stays cheap against a large overdue backlog.
+//
+// Run with:
+// bun run experiments/explainResetCount.ts
+// Options (env vars):
+// RESET_COUNT_CAP=1000000 count cap (default 1,000,000, same as the cron)
+
+const main = async () => {
+ const cap = Number(process.env.RESET_COUNT_CAP ?? 1_000_000);
+ const dueBefore = Date.now();
+ const { db } = initDrizzle();
+
+ // The bunfig preload overlays server/.env.local (worktree Neon branch) on
+ // top of Infisical — run with PW_MODE=1 to skip the overlay and hit the
+ // Infisical-provided DB (e.g. prod).
+ const dbHost = new URL(process.env.DATABASE_URL ?? "").host;
+ console.log(`DB target: ${dbHost}\n`);
+
+ for (const run of ["cold", "warm"]) {
+ const start = performance.now();
+ const result = await countResetEligibleCustomerEntitlements({
+ db,
+ dueBefore,
+ cap,
+ });
+ const elapsed = performance.now() - start;
+ console.log(
+ `[count][${run}] count=${result.count}, capped=${result.capped}, wall-clock=${elapsed.toFixed(0)}ms`,
+ );
+ }
+
+ console.log("\n--- EXPLAIN (ANALYZE, BUFFERS) ---");
+ const explainResult = await db.execute(
+ sql`EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) ${buildCountResetEligibleQuery({ dueBefore, cap })}`,
+ );
+ for (const row of explainResult) {
+ const line = (row as Record)["QUERY PLAN"];
+ if (typeof line === "string") console.log(line);
+ }
+
+ process.exit(0);
+};
+
+await main();
diff --git a/server/experiments/explainResetScan.ts b/server/experiments/explainResetScan.ts
new file mode 100644
index 0000000000..774c4f76ad
--- /dev/null
+++ b/server/experiments/explainResetScan.ts
@@ -0,0 +1,127 @@
+import "dotenv/config";
+import { sql } from "drizzle-orm";
+import { loadLocalEnv } from "../src/utils/envUtils";
+
+loadLocalEnv();
+
+const { initDrizzle } = await import("../src/db/initDrizzle");
+const {
+ buildResetEligiblePageQuery,
+ getResetEligibleCustomerEntitlementsPage,
+} = await import(
+ "../src/internal/customers/cusProducts/cusEnts/repos/getResetEligibleCustomerEntitlementsPage"
+);
+const { countResetEligibleCustomerEntitlements } = await import(
+ "../src/internal/customers/cusProducts/cusEnts/repos/countResetEligibleCustomerEntitlements"
+);
+
+// Benchmarks the REAL reset scan (the exact repo query the V2 cron runs,
+// including the reset_by_invoice + expires_at predicate):
+// 1. single page at several batch sizes (cold + warm)
+// 2. cursor walk: N pages deep, to verify keyset pagination stays flat
+// 3. EXPLAIN of the exact statement
+//
+// NOTE: requires migration 0047 (reset_by_invoice column +
+// idx_customer_entitlements_reset_scan) on the target DB. The plan should use
+// idx_customer_entitlements_reset_scan with NO Incremental Sort node.
+//
+// Run with (PW_MODE=1 skips the .env.local worktree-DB overlay):
+// PW_MODE=1 infisical run --env=prod --recursive -- bun run experiments/explainResetScan.ts
+// Options (env vars):
+// BATCH_SIZES=1000,10000,100000 page sizes to test
+// WALK_PAGE_SIZE=10000 cursor-walk page size
+// WALK_PAGES=20 cursor-walk page count
+// RUN_BACKLOG_COUNT=true also run the capped backlog count
+
+const main = async () => {
+ const batchSizes = (process.env.BATCH_SIZES ?? "1000,10000,100000")
+ .split(",")
+ .map(Number);
+ const walkPageSize = Number(process.env.WALK_PAGE_SIZE ?? 10_000);
+ const walkPages = Number(process.env.WALK_PAGES ?? 20);
+ const dueBefore = Date.now();
+ const { db } = initDrizzle();
+
+ const dbHost = new URL(process.env.DATABASE_URL ?? "").host;
+ console.log(`DB target: ${dbHost}\n`);
+
+ // 1. Single page at each batch size. Wall-clock includes network transfer
+ // of the rows, so big batches partly measure payload size, not DB work —
+ // the EXPLAIN execution time at the end isolates the DB-side cost.
+ console.log("--- single page per batch size (cold, warm) ---");
+ for (const limit of batchSizes) {
+ for (const run of ["cold", "warm"]) {
+ const start = performance.now();
+ const page = await getResetEligibleCustomerEntitlementsPage({
+ db,
+ dueBefore,
+ cursor: null,
+ limit,
+ });
+ const elapsed = performance.now() - start;
+ console.log(
+ `[size=${limit}][${run}] rows=${page.length}, wall-clock=${elapsed.toFixed(0)}ms`,
+ );
+ }
+ }
+
+ // 2. Cursor walk: page N should cost the same as page 1.
+ console.log(
+ `\n--- cursor walk: ${walkPages} pages x ${walkPageSize} rows ---`,
+ );
+ let cursor: { nextResetAt: number; id: string } | null = null;
+ for (let page = 1; page <= walkPages; page++) {
+ const start = performance.now();
+ const rows = await getResetEligibleCustomerEntitlementsPage({
+ db,
+ dueBefore,
+ cursor,
+ limit: walkPageSize,
+ });
+ const elapsed = performance.now() - start;
+ console.log(
+ `[page ${page}] rows=${rows.length}, wall-clock=${elapsed.toFixed(0)}ms, depth=${(page * walkPageSize).toLocaleString()}`,
+ );
+ if (rows.length < walkPageSize) {
+ console.log("(drained)");
+ break;
+ }
+ const lastRow = rows[rows.length - 1];
+ cursor = { nextResetAt: lastRow.nextResetAt, id: lastRow.id };
+ }
+
+ // 3. DB-side cost of the biggest batch size, isolated from network.
+ const maxSize = Math.max(...batchSizes);
+ console.log(`\n--- EXPLAIN (ANALYZE, BUFFERS), size=${maxSize} ---`);
+ const explainResult = await db.execute(
+ sql`EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) ${buildResetEligiblePageQuery(
+ {
+ db,
+ dueBefore,
+ cursor: null,
+ limit: maxSize,
+ },
+ )}`,
+ );
+ for (const row of explainResult) {
+ const line = (row as Record)["QUERY PLAN"];
+ if (typeof line === "string") console.log(line);
+ }
+
+ if (process.env.RUN_BACKLOG_COUNT === "true") {
+ const start = performance.now();
+ const result = await countResetEligibleCustomerEntitlements({
+ db,
+ dueBefore,
+ cap: 1_000_000,
+ });
+ const elapsed = performance.now() - start;
+ console.log(
+ `\n[backlog] overdue-eligible rows=${result.count} (capped=${result.capped}), wall-clock=${elapsed.toFixed(0)}ms`,
+ );
+ }
+
+ process.exit(0);
+};
+
+await main();
diff --git a/server/src/_luaScriptsV2/syncDirty/claimSyncDirty.lua b/server/src/_luaScriptsV2/syncDirty/claimSyncDirty.lua
new file mode 100644
index 0000000000..e5979e4fdd
--- /dev/null
+++ b/server/src/_luaScriptsV2/syncDirty/claimSyncDirty.lua
@@ -0,0 +1,39 @@
+-- Atomically claim the customer's dirty state for a drain: move dirty -> claim,
+-- merging any leftover claim from a crashed worker (uw:* snapshots keep the
+-- newer ts; other marker fields are a no-op union). Stamps a fresh generation
+-- token for compare-and-delete, clears the signal marker so post-claim writes
+-- re-signal, and returns the full claim hash (or nil when empty).
+-- KEYS[1] dirty hash, KEYS[2] claim hash, KEYS[3] signal marker
+-- ARGV[1] claim TTL seconds, ARGV[2] generation token
+local dirtyKey = KEYS[1]
+local claimKey = KEYS[2]
+local signalKey = KEYS[3]
+local claimTtl = tonumber(ARGV[1])
+local generation = ARGV[2]
+if redis.call('EXISTS', dirtyKey) == 1 then
+ if redis.call('EXISTS', claimKey) == 1 then
+ local fields = redis.call('HGETALL', dirtyKey)
+ for i = 1, #fields, 2 do
+ local field = fields[i]
+ local value = fields[i + 1]
+ if string.sub(field, 1, 3) == 'uw:' then
+ local existing = redis.call('HGET', claimKey, field)
+ if not existing or cjson.decode(value).ts >= cjson.decode(existing).ts then
+ redis.call('HSET', claimKey, field, value)
+ end
+ else
+ redis.call('HSET', claimKey, field, value)
+ end
+ end
+ redis.call('DEL', dirtyKey)
+ else
+ redis.call('RENAME', dirtyKey, claimKey)
+ end
+end
+redis.call('DEL', signalKey)
+if redis.call('EXISTS', claimKey) == 1 then
+ redis.call('HSET', claimKey, '__gen', generation)
+ redis.call('EXPIRE', claimKey, claimTtl)
+ return redis.call('HGETALL', claimKey)
+end
+return nil
diff --git a/server/src/_luaScriptsV2/syncDirty/clearSyncClaim.lua b/server/src/_luaScriptsV2/syncDirty/clearSyncClaim.lua
new file mode 100644
index 0000000000..f09bdd8e74
--- /dev/null
+++ b/server/src/_luaScriptsV2/syncDirty/clearSyncClaim.lua
@@ -0,0 +1,7 @@
+-- Compare-and-delete: remove the claim only if the generation token still
+-- matches (a redelivered worker may have re-claimed and merged new state).
+-- KEYS[1] claim hash · ARGV[1] generation token
+if redis.call('HGET', KEYS[1], '__gen') == ARGV[1] then
+ return redis.call('DEL', KEYS[1])
+end
+return 0
diff --git a/server/src/_luaScriptsV2/syncDirty/markSyncDirty.lua b/server/src/_luaScriptsV2/syncDirty/markSyncDirty.lua
new file mode 100644
index 0000000000..492c715b4d
--- /dev/null
+++ b/server/src/_luaScriptsV2/syncDirty/markSyncDirty.lua
@@ -0,0 +1,27 @@
+-- Merge selector/snapshot fields into the customer's sync dirty hash, refresh
+-- its TTL, then attempt to set the signal marker (NX + TTL). Returns 1 when the
+-- marker was newly set (no signal believed in flight), else 0.
+-- uw:* fields carry {ts, snapshot} JSON: an older snapshot never overwrites a
+-- newer one (cross-process batches can arrive out of order).
+-- KEYS[1] dirty hash, KEYS[2] signal marker
+-- ARGV[1] signal TTL seconds, ARGV[2] dirty TTL seconds, ARGV[3..] field/value pairs
+local dirtyKey = KEYS[1]
+local signalKey = KEYS[2]
+local signalTtl = tonumber(ARGV[1])
+local dirtyTtl = tonumber(ARGV[2])
+for i = 3, #ARGV, 2 do
+ local field = ARGV[i]
+ local value = ARGV[i + 1]
+ if string.sub(field, 1, 3) == 'uw:' then
+ local existing = redis.call('HGET', dirtyKey, field)
+ if not existing or cjson.decode(value).ts >= cjson.decode(existing).ts then
+ redis.call('HSET', dirtyKey, field, value)
+ end
+ else
+ redis.call('HSET', dirtyKey, field, value)
+ end
+end
+redis.call('EXPIRE', dirtyKey, dirtyTtl)
+local set = redis.call('SET', signalKey, '1', 'NX', 'EX', signalTtl)
+if set then return 1 end
+return 0
diff --git a/server/src/cron.ts b/server/src/cron.ts
index 2f7278956c..49185408bf 100644
--- a/server/src/cron.ts
+++ b/server/src/cron.ts
@@ -7,7 +7,10 @@ await warmupRegionalRedis();
// Edge config modules self-register on import (cron reads redis-v2-cache
// so resolveRedisV2 picks the right instance on each ctx build).
await import("./internal/misc/redisV2Cache/redisV2CacheStore.js");
+await import("./internal/misc/mainRedisCache/mainRedisCacheStore.js");
await import("./internal/misc/cacheV2Ramp/cacheV2RampStore.js");
+await import("./internal/misc/resetJob/resetJobStore.js");
+await import("./internal/misc/resetJobV2/resetJobV2Store.js");
const { logger } = await import("./external/logtail/logtailUtils.js");
const { startAllEdgeConfigPolling } = await import(
"./internal/misc/edgeConfig/edgeConfigRegistry.js"
diff --git a/server/src/cron/cronInit.ts b/server/src/cron/cronInit.ts
index c2054a86af..a4df2a5091 100644
--- a/server/src/cron/cronInit.ts
+++ b/server/src/cron/cronInit.ts
@@ -4,6 +4,8 @@ import { initDrizzle } from "../db/initDrizzle.js";
import { startPgPoolMonitor, stopPgPoolMonitor } from "../db/pgPoolMonitor.js";
import { runDbProbes } from "../db/probes/runDbProbes.js";
import { logger } from "../external/logtail/logtailUtils.js";
+import { runResetLoopV2 } from "../internal/balances/batchReset/runResetLoopV2.js";
+import { stopAllEdgeConfigPolling } from "../internal/misc/edgeConfig/edgeConfigRegistry.js";
import {
describeSlotGate,
isActiveSlot,
@@ -17,7 +19,7 @@ import { runInvoiceCron } from "./invoiceCron/runInvoiceCron.js";
import { runOneOffCleanup } from "./oneoffCron/runOneOffCleanup.js";
import { runOneOffExpiry } from "./oneoffCron/runOneOffExpiry.js";
import { runProductCron } from "./productCron/runProductCron.js";
-import { runResetCron } from "./resetCron/runResetCron.js";
+import { runResetLoop } from "./resetCron/runResetLoop.js";
import { runSeatSyncCron } from "./seatSyncCron/runSeatSyncCron.js";
import type { CronContext } from "./utils/CronContext.js";
@@ -30,6 +32,9 @@ const { db: probeDb, client: probeClient } = initDrizzle({
startPgPoolMonitor();
startBlueGreenHeartbeat({ db, logger, serviceName: "cron" });
+const ctx: CronContext = { db, logger };
+let shuttingDown = false;
+
const logCronHeartbeat = (job = "main") => {
logger.info(
{
@@ -45,6 +50,8 @@ const logCronHeartbeat = (job = "main") => {
};
const shouldRunTick = () => {
+ if (shuttingDown) return false;
+
if (process.env.DISABLE_CRON === "true") {
console.log(`Cron disabled!`);
return false;
@@ -69,13 +76,8 @@ const main = async () => {
logCronHeartbeat();
- const ctx: CronContext = {
- db,
- logger,
- };
await Promise.all([
runProductCron({ ctx }),
- runResetCron({ ctx }),
runInvoiceCron({ ctx }),
runOneOffExpiry({ ctx }),
// runClearExpiredResetCron({ ctx }),
@@ -89,10 +91,6 @@ const oneOffCleanupTick = async () => {
logCronHeartbeat("one_off_cleanup");
- const ctx: CronContext = {
- db,
- logger,
- };
await Promise.all([runOneOffCleanup({ ctx }), runSeatSyncCron({ ctx })]);
};
@@ -131,22 +129,32 @@ main();
oneOffCleanupTick();
dbProbesTick();
-process.on("SIGTERM", async () => {
- console.log("Received SIGTERM signal, closing database connection...");
- stopPgPoolMonitor();
- stopBlueGreenHeartbeat({ serviceName: "cron" });
- stopBlueGreenSlotStorePolling({ serviceName: "cron" });
- await client.end();
- await probeClient.end();
- process.exit(0);
+// V1 and V2 loops are fully independent, each gated by its own edge config
+// (reset-job / reset-job-v2), so either can be flipped without a deploy.
+const resetLoopController = new AbortController();
+const resetLoopPromise = runResetLoop({
+ ctx,
+ signal: resetLoopController.signal,
+});
+const resetLoopV2Promise = runResetLoopV2({
+ ctx,
+ signal: resetLoopController.signal,
});
-process.on("SIGINT", async () => {
- console.log("Received SIGINT signal, closing database connection...");
+const shutdown = async (signal: string) => {
+ if (shuttingDown) return;
+ shuttingDown = true;
+ console.log(`Received ${signal} signal, closing database connection...`);
+ resetLoopController.abort();
stopPgPoolMonitor();
stopBlueGreenHeartbeat({ serviceName: "cron" });
stopBlueGreenSlotStorePolling({ serviceName: "cron" });
+ stopAllEdgeConfigPolling();
+ await Promise.all([resetLoopPromise, resetLoopV2Promise]);
await client.end();
await probeClient.end();
process.exit(0);
-});
+};
+
+process.on("SIGTERM", () => void shutdown("SIGTERM"));
+process.on("SIGINT", () => void shutdown("SIGINT"));
diff --git a/server/src/cron/resetCron/runResetBatch.ts b/server/src/cron/resetCron/runResetBatch.ts
new file mode 100644
index 0000000000..32eceb2ab7
--- /dev/null
+++ b/server/src/cron/resetCron/runResetBatch.ts
@@ -0,0 +1,101 @@
+import {
+ type AppEnv,
+ type Feature,
+ notNullish,
+ type Organization,
+ type ResetCusEnt,
+} from "@autumn/shared";
+import * as Sentry from "@sentry/bun";
+import pLimit from "p-limit";
+import { buildFullSubjectOrgEnvKey } from "@/internal/customers/cache/fullSubject/builders/buildFullSubjectOrgEnvKey.js";
+import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService";
+import { getResetJobConfig } from "@/internal/misc/resetJob/resetJobStore.js";
+import { OrgService } from "@/internal/orgs/OrgService.js";
+import type { CronContext } from "../utils/CronContext";
+import { clearCusEntsFromCache } from "./clearCusEntsFromCache";
+import { resetCustomerEntitlement } from "./resetCustomerEntitlement";
+
+const RESET_CONCURRENCY = 25;
+
+export const runResetBatch = async ({ ctx }: { ctx: CronContext }) => {
+ const { db, logger } = ctx;
+ const startedAt = Date.now();
+ const { batchSize } = getResetJobConfig();
+ const limitConcurrency = pLimit(RESET_CONCURRENCY);
+ const orgs = new Map();
+ const cusEnts = await CusEntService.getActiveResetPassed({
+ db,
+ batchSize,
+ limit: batchSize,
+ includeSeparateIntervalResets: false,
+ });
+
+ const uniqueOrgEnvs = new Map();
+ for (const cusEnt of cusEnts) {
+ const { env, org_id: orgId } = cusEnt.customer;
+ uniqueOrgEnvs.set(buildFullSubjectOrgEnvKey({ orgId, env }), {
+ orgId,
+ env,
+ });
+ }
+
+ await Promise.all(
+ [...uniqueOrgEnvs.values()].map(({ orgId, env }) =>
+ limitConcurrency(async () => {
+ try {
+ const org = await OrgService.getWithFeatures({ db, orgId, env });
+ if (org) orgs.set(buildFullSubjectOrgEnvKey({ orgId, env }), org);
+ } catch (error) {
+ logger.error(
+ { jobName: "reset-cus-ents", orgId, env, err: error },
+ "[reset-cus-ents] failed to load org",
+ );
+ Sentry.captureException(error, {
+ extra: { orgId, env, context: "runResetBatch.getOrgWithFeatures" },
+ });
+ }
+ }),
+ ),
+ );
+
+ const updatedCusEnts: ResetCusEnt[] = [];
+ const results = await Promise.all(
+ cusEnts.map((cusEnt) =>
+ limitConcurrency(async () => {
+ const org = orgs.get(
+ buildFullSubjectOrgEnvKey({
+ orgId: cusEnt.customer.org_id,
+ env: cusEnt.customer.env,
+ }),
+ );
+ if (!org) return;
+
+ return resetCustomerEntitlement({
+ ctx,
+ org: org.org,
+ cusEnt,
+ updatedCusEnts,
+ persistFreeOverage: org.org.config.persist_free_overage ?? false,
+ });
+ }),
+ ),
+ );
+
+ const toUpsert = results.filter(notNullish);
+ if (toUpsert.length > 0) {
+ await CusEntService.upsert({ db, data: toUpsert });
+ }
+ await clearCusEntsFromCache({ cusEnts: updatedCusEnts });
+
+ const result = {
+ batchSize,
+ fetched: cusEnts.length,
+ upserted: toUpsert.length,
+ durationMs: Date.now() - startedAt,
+ };
+ logger.info(
+ { jobName: "reset-cus-ents", ...result },
+ "[reset-cus-ents] batch finished",
+ );
+ return result;
+};
diff --git a/server/src/cron/resetCron/runResetCron.ts b/server/src/cron/resetCron/runResetCron.ts
deleted file mode 100644
index e2b21c44cf..0000000000
--- a/server/src/cron/resetCron/runResetCron.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-import {
- type AppEnv,
- type Feature,
- notNullish,
- type Organization,
- type ResetCusEnt,
-} from "@autumn/shared";
-import { UTCDate } from "@date-fns/utc";
-import * as Sentry from "@sentry/bun";
-import { format } from "date-fns";
-import { buildFullSubjectOrgEnvKey } from "@/internal/customers/cache/fullSubject/builders/buildFullSubjectOrgEnvKey.js";
-import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService";
-import { OrgService } from "@/internal/orgs/OrgService.js";
-import type { CronContext } from "../utils/CronContext";
-import { clearCusEntsFromCache } from "./clearCusEntsFromCache";
-import { resetCustomerEntitlement } from "./resetCustomerEntitlement";
-
-export const runResetCron = async ({ ctx }: { ctx: CronContext }) => {
- const { db, logger } = ctx;
-
- const maxIterations = 10;
- const timeoutMs = 60_000; // 1 minute
- const startTime = Date.now();
-
- const orgWithFeaturesCache = new Map<
- string,
- { org: Organization; features: Feature[] }
- >();
-
- const getOrgWithFeatures = async ({
- orgId,
- env,
- }: {
- orgId: string;
- env: AppEnv;
- }): Promise<{ org: Organization; features: Feature[] } | undefined> => {
- const cacheKey = buildFullSubjectOrgEnvKey({ orgId, env });
- const cached = orgWithFeaturesCache.get(cacheKey);
- if (cached) return cached;
-
- try {
- const orgWithFeatures = await OrgService.getWithFeatures({
- db,
- orgId,
- env,
- });
- if (!orgWithFeatures) return undefined;
- orgWithFeaturesCache.set(cacheKey, orgWithFeatures);
- return orgWithFeatures;
- } catch (error) {
- console.error(
- `Reset cron: failed to fetch org with features for orgId=${orgId}, env=${env}, skipping cusEnts for this org`,
- error,
- );
-
- logger.error(
- `Reset cron: failed to fetch org with features for orgId=${orgId}, env=${env}, skipping cusEnts for this org ${error}`,
- );
-
- Sentry.captureException(error, {
- extra: { orgId, env, context: "runResetCron.getOrgWithFeatures" },
- });
- return undefined;
- }
- };
-
- try {
- let iteration = 0;
- let totalFetched = 0;
- let totalUpserted = 0;
-
- logger.info({ jobName: "reset-cus-ents" }, "[reset-cus-ents] started");
-
- while (iteration < maxIterations && Date.now() - startTime < timeoutMs) {
- iteration++;
-
- const fetchStartedAt = Date.now();
- const cusEnts = await CusEntService.getActiveResetPassed({
- db,
- batchSize: 5_000,
- limit: 5_000,
- includeSeparateIntervalResets: false,
- });
- totalFetched += cusEnts.length;
-
- logger.info(
- {
- jobName: "reset-cus-ents",
- iteration,
- fetched: cusEnts.length,
- fetchDurationMs: Date.now() - fetchStartedAt,
- },
- `[reset-cus-ents] iteration ${iteration}: fetched ${cusEnts.length} in ${Date.now() - fetchStartedAt}ms`,
- );
-
- if (cusEnts.length < 5_000) {
- logger.info(
- { jobName: "reset-cus-ents", fetched: cusEnts.length },
- `[reset-cus-ents] only ${cusEnts.length} entitlements to reset, skipping (lazy reset will handle)`,
- );
- break;
- }
-
- const batchSize = 1000;
- for (let i = 0; i < cusEnts.length; i += batchSize) {
- const batch = cusEnts.slice(i, i + batchSize);
-
- const uniqueOrgEnvs = new Map();
- for (const customerEntitlement of batch) {
- const env = customerEntitlement.customer.env;
- const orgId = customerEntitlement.customer.org_id;
- uniqueOrgEnvs.set(buildFullSubjectOrgEnvKey({ orgId, env }), {
- orgId,
- env,
- });
- }
- await Promise.all(
- [...uniqueOrgEnvs.values()].map(({ orgId, env }) =>
- getOrgWithFeatures({ orgId, env }),
- ),
- );
-
- const batchResets = [];
- const updatedCusEnts: ResetCusEnt[] = [];
- for (const cusEnt of batch) {
- const orgWithFeatures = orgWithFeaturesCache.get(
- buildFullSubjectOrgEnvKey({
- orgId: cusEnt.customer.org_id,
- env: cusEnt.customer.env,
- }),
- );
- if (!orgWithFeatures) continue;
-
- batchResets.push(
- resetCustomerEntitlement({
- ctx,
- org: orgWithFeatures.org,
- cusEnt: cusEnt,
- updatedCusEnts,
- persistFreeOverage:
- orgWithFeatures.org.config.persist_free_overage ?? false,
- }),
- );
- }
-
- const results = await Promise.all(batchResets);
-
- const toUpsert = results.filter(notNullish);
- await CusEntService.upsert({
- db,
- data: toUpsert,
- });
- totalUpserted += toUpsert.length;
-
- await clearCusEntsFromCache({
- cusEnts: updatedCusEnts,
- });
- }
- }
-
- logger.info(
- {
- jobName: "reset-cus-ents",
- iterations: iteration,
- totalFetched,
- totalUpserted,
- durationMs: Date.now() - startTime,
- },
- `[reset-cus-ents] finished: fetched ${totalFetched}, upserted ${totalUpserted} (monthly/yearly resets update in place and are not counted here) across ${iteration} iteration(s) in ${Date.now() - startTime}ms`,
- );
- console.log(
- "FINISHED RESET CRON:",
- format(new UTCDate(), "yyyy-MM-dd HH:mm:ss"),
- );
- } catch (error) {
- logger.error(
- {
- jobName: "reset-cus-ents",
- durationMs: Date.now() - startTime,
- err: error,
- },
- "[reset-cus-ents] failed",
- );
- console.error("Error getting entitlements for reset:", error);
- return;
- }
-};
diff --git a/server/src/cron/resetCron/runResetLoop.ts b/server/src/cron/resetCron/runResetLoop.ts
new file mode 100644
index 0000000000..7dd15716ec
--- /dev/null
+++ b/server/src/cron/resetCron/runResetLoop.ts
@@ -0,0 +1,69 @@
+import { ms } from "@autumn/shared";
+import * as Sentry from "@sentry/bun";
+import { isResetJobEnabled } from "@/internal/misc/resetJob/resetJobStore.js";
+import { isActiveSlot } from "@/queue/blueGreen/blueGreenGate.js";
+import type { CronContext } from "../utils/CronContext.js";
+import { runResetBatch } from "./runResetBatch.js";
+
+const ACTIVE_DELAY_MS = ms.seconds(1);
+const IDLE_DELAY_MS = ms.seconds(5);
+
+type ResetBatchResult = Awaited>;
+
+const waitForNextBatch = async ({
+ delayMs,
+ signal,
+}: {
+ delayMs: number;
+ signal: AbortSignal;
+}) => {
+ if (signal.aborted) return;
+ await new Promise((resolve) => {
+ const finish = () => {
+ clearTimeout(timer);
+ signal.removeEventListener("abort", finish);
+ resolve();
+ };
+ const timer = setTimeout(finish, delayMs);
+ signal.addEventListener("abort", finish, { once: true });
+ });
+};
+
+export const runResetLoop = async ({
+ ctx,
+ signal,
+ isEnabled = isResetJobEnabled,
+ isActive = () => isActiveSlot({ serviceName: "cron" }),
+ runBatch = runResetBatch,
+ wait = waitForNextBatch,
+}: {
+ ctx: CronContext;
+ signal: AbortSignal;
+ isEnabled?: () => boolean;
+ isActive?: () => boolean;
+ runBatch?: (params: { ctx: CronContext }) => Promise;
+ wait?: (params: { delayMs: number; signal: AbortSignal }) => Promise;
+}) => {
+ while (!signal.aborted) {
+ let delayMs = IDLE_DELAY_MS;
+
+ if (process.env.DISABLE_CRON !== "true" && isEnabled() && isActive()) {
+ try {
+ const result = await runBatch({ ctx });
+ if (result.fetched === result.batchSize) {
+ delayMs = ACTIVE_DELAY_MS;
+ }
+ } catch (error) {
+ ctx.logger.error(
+ { jobName: "reset-cus-ents", err: error },
+ "[reset-cus-ents] batch failed",
+ );
+ Sentry.captureException(error, {
+ extra: { context: "runResetLoop.runBatch" },
+ });
+ }
+ }
+
+ await wait({ delayMs, signal });
+ }
+};
diff --git a/server/src/cron/resetV2.ts b/server/src/cron/resetV2.ts
new file mode 100644
index 0000000000..3944bf8cb0
--- /dev/null
+++ b/server/src/cron/resetV2.ts
@@ -0,0 +1,141 @@
+import { spawn } from "node:child_process";
+import { fileURLToPath } from "node:url";
+
+type ResetEnvironment = "staging" | "prod";
+
+const WRAPPED_ENV_VAR = "AUTUMN_RESET_V2_WRAPPED";
+const ENV_FILES: Record = {
+ staging: ".env.staging",
+ prod: ".env.prod",
+};
+
+const parseEnvironment = (): ResetEnvironment => {
+ const environment = process.argv[2];
+ if (environment === "staging" || environment === "prod") return environment;
+
+ console.error("Usage: bun reset-v2 ");
+ process.exit(2);
+};
+
+const runWithInfisical = async ({
+ environment,
+}: {
+ environment: ResetEnvironment;
+}) => {
+ const child = spawn(
+ "infisical",
+ [
+ "run",
+ `--env=${environment}`,
+ "--recursive",
+ "--",
+ "bun",
+ fileURLToPath(import.meta.url),
+ environment,
+ ],
+ {
+ stdio: "inherit",
+ env: {
+ ...process.env,
+ [WRAPPED_ENV_VAR]: "1",
+ ENV_FILE: ENV_FILES[environment],
+ NODE_ENV: "development",
+ },
+ },
+ );
+
+ const exitCode = await new Promise((resolve) => {
+ child.on("close", (code) => resolve(code ?? 1));
+ child.on("error", (error) => {
+ console.error(`Failed to start Infisical: ${error.message}`);
+ resolve(1);
+ });
+ });
+ process.exit(exitCode);
+};
+
+const runResetV2 = async ({
+ environment,
+}: {
+ environment: ResetEnvironment;
+}) => {
+ await import("../sentry.js");
+ await import("../internal/misc/resetJobV2/resetJobV2Store.js");
+
+ const { initDrizzle } = await import("../db/initDrizzle.js");
+ const { startPgPoolMonitor, stopPgPoolMonitor } = await import(
+ "../db/pgPoolMonitor.js"
+ );
+ const { logger } = await import("../external/logtail/logtailUtils.js");
+ const { runResetLoopV2 } = await import(
+ "../internal/balances/batchReset/runResetLoopV2.js"
+ );
+ const { getResetJobV2Config, getResetJobV2ConfigStatus } = await import(
+ "../internal/misc/resetJobV2/resetJobV2Store.js"
+ );
+ const { startAllEdgeConfigPolling, stopAllEdgeConfigPolling } = await import(
+ "../internal/misc/edgeConfig/edgeConfigRegistry.js"
+ );
+
+ await startAllEdgeConfigPolling({ logger });
+
+ const { db, client } = initDrizzle({
+ name: "reset-cron-v2",
+ maxConnections: 10,
+ });
+ startPgPoolMonitor();
+
+ const controller = new AbortController();
+ const loopPromise = runResetLoopV2({
+ ctx: { db, logger },
+ signal: controller.signal,
+ });
+ let shuttingDown = false;
+
+ // Everything under `data`: the express dataset is at its Axiom column
+ // limit, so new top-level (flattened) field names get the whole ingest
+ // batch rejected.
+ logger.info("[reset-cus-ents-v2] dedicated scanner started", {
+ jobName: "reset-cus-ents-v2",
+ data: {
+ environment,
+ config: getResetJobV2Config(),
+ configStatus: getResetJobV2ConfigStatus(),
+ },
+ });
+
+ // The scanner must never die because telemetry hiccuped (e.g. an Axiom
+ // ingest rejection inside the pino transport becomes an unhandled
+ // rejection, which kills the process by default).
+ process.on("unhandledRejection", (reason) => {
+ console.error("[reset-cus-ents-v2] unhandled rejection (ignored):", reason);
+ });
+
+ const shutdown = async (signal: string) => {
+ if (shuttingDown) return;
+ shuttingDown = true;
+ logger.info(`[reset-cus-ents-v2] received ${signal}, shutting down`);
+ controller.abort();
+ stopPgPoolMonitor();
+ stopAllEdgeConfigPolling();
+ await loopPromise;
+ await client.end();
+ };
+
+ process.once("SIGINT", () => void shutdown("SIGINT"));
+ process.once("SIGTERM", () => void shutdown("SIGTERM"));
+
+ await loopPromise;
+};
+
+const main = async () => {
+ const environment = parseEnvironment();
+ if (process.env[WRAPPED_ENV_VAR] !== "1") {
+ await runWithInfisical({ environment });
+ return;
+ }
+
+ await runResetV2({ environment });
+};
+
+await main();
diff --git a/server/src/db/shed503OnTransientError.ts b/server/src/db/shed503OnTransientError.ts
index e2d7c44822..749cda8acb 100644
--- a/server/src/db/shed503OnTransientError.ts
+++ b/server/src/db/shed503OnTransientError.ts
@@ -7,10 +7,12 @@ export const shed503OnTransientError = async ({
ctx,
source,
run,
+ onTransientError,
}: {
ctx: AutumnContext;
source: string;
run: () => T | Promise;
+ onTransientError?: (error: unknown) => Promise;
}): Promise => {
try {
return await run();
@@ -22,6 +24,14 @@ export const shed503OnTransientError = async ({
type: `${source}_fail_open`,
error,
});
+ try {
+ await onTransientError?.(error);
+ } catch (recoveryError) {
+ ctx.logger.error(
+ `[${source}] Failed to capture transient error for recovery`,
+ { error: recoveryError },
+ );
+ }
throw new RecaseError({
message: "Service is temporarily unavailable, please retry shortly.",
code: "service_unavailable",
diff --git a/server/src/external/autumn/autumnCli.ts b/server/src/external/autumn/autumnCli.ts
index 35fc321541..cf156b8ce0 100644
--- a/server/src/external/autumn/autumnCli.ts
+++ b/server/src/external/autumn/autumnCli.ts
@@ -1123,6 +1123,7 @@ export class AutumnInt {
dry_run?: boolean;
only?: string[];
limit?: number;
+ /** @deprecated Migration concurrency is fleet-managed. */
concurrency?: number;
lazy_run?: boolean;
retry_item_statuses?: ("failed" | "skipped")[];
diff --git a/server/src/external/aws/s3/adminS3Config.ts b/server/src/external/aws/s3/adminS3Config.ts
index 274084d807..b8c9f34dac 100644
--- a/server/src/external/aws/s3/adminS3Config.ts
+++ b/server/src/external/aws/s3/adminS3Config.ts
@@ -10,8 +10,13 @@ export const ADMIN_RATE_LIMIT_REDIS_ALLOWLIST_CONFIG_KEY =
"admin/rate-limit-redis-allowlist-config.json";
export const ADMIN_REDIS_V2_CACHE_CONFIG_KEY =
"admin/redis-v2-cache-config.json";
+export const ADMIN_MAIN_REDIS_CACHE_CONFIG_KEY =
+ "admin/main-redis-cache-config.json";
export const ADMIN_CACHE_V2_RAMP_CONFIG_KEY = "admin/cache-v2-ramp-config.json";
export const ADMIN_JOB_QUEUE_CONFIG_KEY = "admin/job-queue-config.json";
+export const ADMIN_BATCH_RESET_CONFIG_KEY = "admin/batch-reset-config.json";
+export const ADMIN_RESET_JOB_CONFIG_KEY = "admin/reset-job-config.json";
+export const ADMIN_RESET_JOB_V2_CONFIG_KEY = "admin/reset-job-v2-config.json";
export const ADMIN_MISCELLANEOUS_EDGE_CONFIG_KEY =
"admin/miscellaneous-edge-config.json";
export const ADMIN_FULL_SUBJECT_GATE_CONFIG_KEY =
@@ -66,6 +71,11 @@ export const getAdminEdgeConfigSources = () => ({
label: "V2 Redis Instance",
key: ADMIN_REDIS_V2_CACHE_CONFIG_KEY,
},
+ {
+ id: "main-redis-cache",
+ label: "Main Redis Instance",
+ key: ADMIN_MAIN_REDIS_CACHE_CONFIG_KEY,
+ },
{
id: "cache-v2-ramp",
label: "Cache V2 Ramp",
@@ -76,6 +86,21 @@ export const getAdminEdgeConfigSources = () => ({
label: "Job Queues",
key: ADMIN_JOB_QUEUE_CONFIG_KEY,
},
+ {
+ id: "batch-resets",
+ label: "Lazy Batch Resets",
+ key: ADMIN_BATCH_RESET_CONFIG_KEY,
+ },
+ {
+ id: "reset-job",
+ label: "Reset Job",
+ key: ADMIN_RESET_JOB_CONFIG_KEY,
+ },
+ {
+ id: "reset-job-v2",
+ label: "Reset Job V2",
+ key: ADMIN_RESET_JOB_V2_CONFIG_KEY,
+ },
{
id: "miscellaneous",
label: "Miscellaneous",
diff --git a/server/src/external/redis/initRedis.ts b/server/src/external/redis/initRedis.ts
index 30e3a91e47..78ceae8874 100644
--- a/server/src/external/redis/initRedis.ts
+++ b/server/src/external/redis/initRedis.ts
@@ -1,11 +1,18 @@
import "./initUtils/redisTypes.js";
export {
+ createDisabledRedis,
createRedisClient,
createRedisConnection,
- createDisabledRedis,
} from "./initUtils/createRedisClient.js";
export {
+ getRedisAvailability,
+ shouldUseRedis,
+ startRedisMonitor,
+ stopRedisMonitor,
+} from "./initUtils/redisAvailability.js";
+export {
+ getFallbackRedis,
getPrimaryRedis,
getRegionalRedis,
redis,
@@ -15,12 +22,6 @@ export {
getConfiguredRegions,
hasRedisConfig,
} from "./initUtils/redisConfig.js";
-export {
- getRedisAvailability,
- shouldUseRedis,
- startRedisMonitor,
- stopRedisMonitor,
-} from "./initUtils/redisAvailability.js";
export {
waitForRedisReady,
warmupRegionalRedis,
diff --git a/server/src/external/redis/initUtils/createRedisClient.ts b/server/src/external/redis/initUtils/createRedisClient.ts
index 098cd4d494..28ca4e4c3a 100644
--- a/server/src/external/redis/initUtils/createRedisClient.ts
+++ b/server/src/external/redis/initUtils/createRedisClient.ts
@@ -1,6 +1,6 @@
import { Redis } from "ioredis";
import { instrumentRedis } from "../otel/instrumentRedis.js";
-import { cacheBackupUrl } from "./redisConfig.js";
+import { redisDnsLookup } from "./redisDnsLookup.js";
import { registerRedisCommands } from "./registerRedisCommands.js";
const REDIS_COMMAND_TIMEOUT_MS =
@@ -22,11 +22,13 @@ const formatRedisEndpoint = ({ cacheUrl }: { cacheUrl: string }) => {
export const createRedisClient = ({
cacheUrl,
region,
+ cacheCert = process.env.CACHE_CERT || null,
supportsUpstashShebang = false,
commandTimeout = REDIS_COMMAND_TIMEOUT_MS,
}: {
cacheUrl: string;
region: string;
+ cacheCert?: string | null;
supportsUpstashShebang?: boolean;
commandTimeout?: number;
}): Redis => {
@@ -34,10 +36,13 @@ export const createRedisClient = ({
`[Redis] ${region}: connecting to ${formatRedisEndpoint({ cacheUrl })}`,
);
+ const usesTls = cacheUrl.startsWith("rediss:");
+
const instance = new Redis(cacheUrl, {
- tls:
- process.env.CACHE_CERT && !cacheBackupUrl
- ? { ca: process.env.CACHE_CERT }
+ tls: cacheCert
+ ? { ca: cacheCert }
+ : usesTls
+ ? { lookup: redisDnsLookup }
: undefined,
family: 4,
keepAlive: 10000,
diff --git a/server/src/external/redis/initUtils/redisClientRegistry.ts b/server/src/external/redis/initUtils/redisClientRegistry.ts
index 620cef5f8b..17a3e3e5a8 100644
--- a/server/src/external/redis/initUtils/redisClientRegistry.ts
+++ b/server/src/external/redis/initUtils/redisClientRegistry.ts
@@ -1,6 +1,12 @@
import type { Redis } from "ioredis";
+import { getActiveMainRedisInstance } from "@/internal/misc/mainRedisCache/mainRedisCacheStore.js";
+import {
+ createMainRedisRouter,
+ selectMainRedisClient,
+} from "../mainRedisRouting.js";
import { createDisabledRedis, createRedisClient } from "./createRedisClient.js";
import {
+ cacheBackupUrl,
currentRegion,
getCacheUrlForRegion,
hasRedisConfig,
@@ -8,17 +14,19 @@ import {
primaryCacheUrl,
} from "./redisConfig.js";
-if (process.env.CACHE_BACKUP_URL?.trim()) {
+if (cacheBackupUrl) {
console.log(
- `[Redis] Using CACHE_BACKUP_URL for all regions (primary region: ${currentRegion})`,
+ `[Redis] CACHE_BACKUP_URL configured as fallback (primary region: ${currentRegion})`,
);
} else if (!hasRedisConfig) {
- console.warn("[Redis] No Redis URL configured. Running in Postgres-only mode.");
+ console.warn(
+ "[Redis] No Redis URL configured. Running in Postgres-only mode.",
+ );
} else if (primaryCacheUrl && getCacheUrlForRegion({ region: currentRegion })) {
console.log(`Using regional cache: ${currentRegion}`);
}
-const primaryRedis =
+const localPrimaryRedis =
hasRedisConfig && primaryCacheUrl
? createRedisClient({
cacheUrl: primaryCacheUrl,
@@ -26,22 +34,37 @@ const primaryRedis =
})
: createDisabledRedis();
-/**
- * The active Redis instance. All consumer code imports this.
- * Normally points to the primary (current region).
- */
-export const redis: Redis = primaryRedis;
+const fallbackRedis = !cacheBackupUrl
+ ? null
+ : cacheBackupUrl === primaryCacheUrl
+ ? localPrimaryRedis
+ : createRedisClient({
+ cacheUrl: cacheBackupUrl,
+ region: `${currentRegion}:fallback`,
+ cacheCert: null,
+ });
+
+export const getFallbackRedis = (): Redis | null => fallbackRedis;
// Lazy-loaded regional Redis instances for cross-region sync
const regionalRedisInstances: Map = new Map();
+let lastLoggedInstance: string | null = null;
+let missingFallbackWarned = false;
+
+export const getRegionalRedisForInstance = ({
+ region,
+ instance,
+}: {
+ region: string;
+ instance: "primary" | "fallback";
+}): Redis => {
+ if (instance === "fallback" && fallbackRedis) return fallbackRedis;
-/** Get Redis instance for a specific region (lazy-loaded) */
-export const getRegionalRedis = (region: string): Redis => {
if (!hasRedisConfig) {
- return primaryRedis;
+ return localPrimaryRedis;
}
if (region === currentRegion) {
- return primaryRedis;
+ return localPrimaryRedis;
}
const cacheUrl = getCacheUrlForRegion({ region });
@@ -50,11 +73,11 @@ export const getRegionalRedis = (region: string): Redis => {
console.warn(
`No cache URL configured for region ${region}, falling back to primary`,
);
- return primaryRedis;
+ return localPrimaryRedis;
}
if (cacheUrl === primaryCacheUrl) {
- return primaryRedis;
+ return localPrimaryRedis;
}
let regionalInstance = regionalRedisInstances.get(region);
@@ -69,5 +92,34 @@ export const getRegionalRedis = (region: string): Redis => {
return regionalInstance;
};
+/** Get the active Redis instance for a region. */
+export const getRegionalRedis = (region: string): Redis => {
+ const activeInstance = getActiveMainRedisInstance();
+ if (activeInstance !== lastLoggedInstance) {
+ console.log(`[Redis] Active main instance: ${activeInstance}`);
+ lastLoggedInstance = activeInstance;
+ }
+ if (
+ activeInstance === "fallback" &&
+ !fallbackRedis &&
+ !missingFallbackWarned
+ ) {
+ console.warn(
+ "[Redis] Fallback selected without CACHE_BACKUP_URL; using primary",
+ );
+ missingFallbackWarned = true;
+ }
+
+ return selectMainRedisClient({
+ activeInstance,
+ primary: () => getRegionalRedisForInstance({ region, instance: "primary" }),
+ fallback: fallbackRedis,
+ });
+};
+
+export const redis: Redis = createMainRedisRouter({
+ resolve: () => getRegionalRedis(currentRegion),
+});
+
/** Get the primary Redis instance (us-west-2) to avoid replication lag issues */
export const getPrimaryRedis = () => getRegionalRedis(PRIMARY_REGION);
diff --git a/server/src/external/redis/initUtils/redisConfig.ts b/server/src/external/redis/initUtils/redisConfig.ts
index 8b8df529bd..9838ae8a98 100644
--- a/server/src/external/redis/initUtils/redisConfig.ts
+++ b/server/src/external/redis/initUtils/redisConfig.ts
@@ -10,29 +10,29 @@ export const currentRegion = process.env.AWS_REGION || REGION_US_WEST_2;
export const cacheBackupUrl = process.env.CACHE_BACKUP_URL?.trim();
-// Map of region to cache URL. When CACHE_BACKUP_URL is set, all regions use it
-// (failover / single backup endpoint).
-const regionToCacheUrl: Record = cacheBackupUrl
- ? {
- [REGION_US_EAST_2]: cacheBackupUrl,
- [REGION_US_WEST_2]: cacheBackupUrl,
- }
- : {
- [REGION_US_EAST_2]: process.env.CACHE_URL_US_EAST,
- [REGION_US_WEST_2]: process.env.CACHE_URL,
- };
+// The backup is edge-selected unless it is the only configured Redis endpoint.
+const regionToCacheUrl: Record = {
+ [REGION_US_EAST_2]: process.env.CACHE_URL_US_EAST,
+ [REGION_US_WEST_2]: process.env.CACHE_URL,
+};
+const hasPrimaryCacheUrls = Object.values(regionToCacheUrl).some(Boolean);
export const primaryCacheUrl =
regionToCacheUrl[currentRegion] || process.env.CACHE_URL || cacheBackupUrl;
export const hasRedisConfig = Boolean(primaryCacheUrl);
+
/** Get all regions that have configured cache URLs */
export const getConfiguredRegions = (): string[] => {
- return ALL_REGIONS.filter((region) => regionToCacheUrl[region]);
+ const configured = ALL_REGIONS.filter((region) => regionToCacheUrl[region]);
+ return hasPrimaryCacheUrls || !cacheBackupUrl ? configured : [...ALL_REGIONS];
};
export const getCacheUrlForRegion = ({ region }: { region: string }) => {
- return regionToCacheUrl[region];
+ return (
+ regionToCacheUrl[region] ||
+ (!hasPrimaryCacheUrls ? cacheBackupUrl : undefined)
+ );
};
export const PRIMARY_REGION = REGION_US_WEST_2;
diff --git a/server/src/external/redis/initUtils/redisDnsLookup.ts b/server/src/external/redis/initUtils/redisDnsLookup.ts
new file mode 100644
index 0000000000..a68598ed30
--- /dev/null
+++ b/server/src/external/redis/initUtils/redisDnsLookup.ts
@@ -0,0 +1,56 @@
+import {
+ lookup as defaultLookup,
+ resolve4 as defaultResolve4,
+ type LookupAddress,
+ type LookupOptions,
+} from "node:dns";
+import type { LookupFunction } from "node:net";
+
+type LookupHost = (
+ hostname: string,
+ options: LookupOptions,
+ callback: (
+ error: NodeJS.ErrnoException | null,
+ address: string | LookupAddress[],
+ family: number,
+ ) => void,
+) => void;
+
+type Resolve4Host = (
+ hostname: string,
+ callback: (error: NodeJS.ErrnoException | null, addresses: string[]) => void,
+) => void;
+
+export const createRedisDnsLookup = ({
+ lookupHost = defaultLookup as LookupHost,
+ resolve4Host = defaultResolve4,
+}: {
+ lookupHost?: LookupHost;
+ resolve4Host?: Resolve4Host;
+} = {}): LookupFunction => {
+ return (hostname, options, callback) => {
+ lookupHost(hostname, options, (lookupError, address, family) => {
+ if (!lookupError) {
+ callback(null, address, family);
+ return;
+ }
+
+ if (lookupError.code !== "ENOTFOUND" || options.all) {
+ callback(lookupError, "", family);
+ return;
+ }
+
+ resolve4Host(hostname, (resolveError, addresses) => {
+ const resolvedAddress = addresses?.[0];
+ if (resolveError || !resolvedAddress) {
+ callback(resolveError ?? lookupError, "", 4);
+ return;
+ }
+
+ callback(null, resolvedAddress, 4);
+ });
+ });
+ };
+};
+
+export const redisDnsLookup = createRedisDnsLookup();
diff --git a/server/src/external/redis/initUtils/redisWarmup.ts b/server/src/external/redis/initUtils/redisWarmup.ts
index 3dc24bdcba..73a49dbd64 100644
--- a/server/src/external/redis/initUtils/redisWarmup.ts
+++ b/server/src/external/redis/initUtils/redisWarmup.ts
@@ -1,5 +1,8 @@
import type { Redis } from "ioredis";
-import { getRegionalRedis } from "./redisClientRegistry.js";
+import {
+ getFallbackRedis,
+ getRegionalRedisForInstance,
+} from "./redisClientRegistry.js";
import { getConfiguredRegions } from "./redisConfig.js";
/** Wait for a Redis instance to be ready */
@@ -40,7 +43,10 @@ export const warmupRegionalRedis = async (): Promise => {
const warmupPromises = regions.map(async (region) => {
try {
- const instance = getRegionalRedis(region);
+ const instance = getRegionalRedisForInstance({
+ region,
+ instance: "primary",
+ });
await waitForRedisReady(instance, region);
} catch (error) {
console.error(`[Redis] ${region}: warmup failed -`, error);
@@ -48,6 +54,15 @@ export const warmupRegionalRedis = async (): Promise => {
}
});
+ const fallback = getFallbackRedis();
+ if (fallback) {
+ warmupPromises.push(
+ waitForRedisReady(fallback, "fallback").catch((error) => {
+ console.error("[Redis] fallback: warmup failed -", error);
+ }),
+ );
+ }
+
await Promise.all(warmupPromises);
try {
diff --git a/server/src/external/redis/mainRedisRouting.ts b/server/src/external/redis/mainRedisRouting.ts
new file mode 100644
index 0000000000..0ac5129f9a
--- /dev/null
+++ b/server/src/external/redis/mainRedisRouting.ts
@@ -0,0 +1,28 @@
+import type { Redis } from "ioredis";
+import type { MainRedisInstanceName } from "@/internal/misc/mainRedisCache/mainRedisCacheSchemas.js";
+
+export const selectMainRedisClient = ({
+ activeInstance,
+ primary,
+ fallback,
+}: {
+ activeInstance: MainRedisInstanceName;
+ primary: () => Redis;
+ fallback: Redis | null;
+}): Redis => (activeInstance === "fallback" && fallback ? fallback : primary());
+
+export const createMainRedisRouter = ({
+ resolve,
+}: {
+ resolve: () => Redis;
+}): Redis =>
+ new Proxy({} as Redis, {
+ get(_target, property) {
+ const redis = resolve();
+ const value = Reflect.get(redis, property, redis);
+ return typeof value === "function" ? value.bind(redis) : value;
+ },
+ set(_target, property, value) {
+ return Reflect.set(resolve(), property, value);
+ },
+ });
diff --git a/server/src/external/redis/resolveRedisV2.ts b/server/src/external/redis/resolveRedisV2.ts
index e5c0c42688..1286770e6f 100644
--- a/server/src/external/redis/resolveRedisV2.ts
+++ b/server/src/external/redis/resolveRedisV2.ts
@@ -14,6 +14,47 @@ import {
let lastLoggedInstance: RedisV2InstanceName | null = null;
let publicRouteWarned = false;
+export type ResolvedRedisV2InstanceName = RedisV2InstanceName | "ramp";
+
+export const getRedisV2ByInstanceName = (name: string): Redis | null => {
+ if (name === "dragonfly") return redisV2Primary;
+ if (name === "ramp") return getRampDestinationRedis();
+ if (name === "upstash" || name === "redis") {
+ return getAlternateRedisV2Instance(name);
+ }
+ return null;
+};
+
+const resolveRedisV2InstanceName = ({
+ activeInstance,
+ customerId,
+}: {
+ activeInstance: RedisV2InstanceName;
+ customerId?: string;
+}): ResolvedRedisV2InstanceName => {
+ if (activeInstance !== "dragonfly") {
+ return getAlternateRedisV2Instance(activeInstance)
+ ? activeInstance
+ : "dragonfly";
+ }
+
+ if (
+ isCacheV2RampEnabled({ customerId }) &&
+ getRampDestinationRedis()
+ ) {
+ return "ramp";
+ }
+ return "dragonfly";
+};
+
+export const getCurrentRedisV2InstanceName = (opts?: {
+ customerId?: string;
+}): ResolvedRedisV2InstanceName =>
+ resolveRedisV2InstanceName({
+ activeInstance: getActiveRedisV2Instance(),
+ customerId: opts?.customerId,
+ });
+
/** Returns the V2 Redis instance for this request.
*
* Routing order:
@@ -36,20 +77,20 @@ export const resolveRedisV2 = (opts?: { customerId?: string }): Redis => {
lastLoggedInstance = activeInstance;
}
- if (activeInstance === "dragonfly") {
- if (isCacheV2RampEnabled({ customerId: opts?.customerId })) {
- const destination = getRampDestinationRedis();
- if (destination) return destination;
- if (!publicRouteWarned) {
- publicRouteWarned = true;
- logger.warn(
- "[resolveRedisV2] cache V2 ramp enabled but destination is not configured (or decryption failed); falling back to primary",
- );
- }
- }
- return redisV2Primary;
+ const instanceName = resolveRedisV2InstanceName({
+ activeInstance,
+ customerId: opts?.customerId,
+ });
+ if (
+ activeInstance === "dragonfly" &&
+ instanceName === "dragonfly" &&
+ isCacheV2RampEnabled({ customerId: opts?.customerId }) &&
+ !publicRouteWarned
+ ) {
+ publicRouteWarned = true;
+ logger.warn(
+ "[resolveRedisV2] cache V2 ramp enabled but destination is not configured (or decryption failed); falling back to primary",
+ );
}
-
- const alternate = getAlternateRedisV2Instance(activeInstance);
- return alternate ?? redisV2Primary;
+ return getRedisV2ByInstanceName(instanceName) ?? redisV2Primary;
};
diff --git a/server/src/external/stripe/common/autumnStripeIdempotency.ts b/server/src/external/stripe/common/autumnStripeIdempotency.ts
new file mode 100644
index 0000000000..a5d7db664f
--- /dev/null
+++ b/server/src/external/stripe/common/autumnStripeIdempotency.ts
@@ -0,0 +1,32 @@
+import { randomUUID } from "node:crypto";
+import type Stripe from "stripe";
+
+export const AUTUMN_STRIPE_IDEMPOTENCY_PREFIX = "autumn:";
+
+/** Unique per call — Stripe rejects a reused key on a different request. */
+export const buildAutumnStripeIdempotencyKey = ({
+ source,
+}: {
+ source?: string;
+}): string =>
+ `${AUTUMN_STRIPE_IDEMPOTENCY_PREFIX}${source ?? "billing"}:${randomUUID()}`;
+
+export const autumnStripeRequestOptions = ({
+ source,
+}: {
+ source?: string;
+} = {}): Stripe.RequestOptions => ({
+ idempotencyKey: buildAutumnStripeIdempotencyKey({ source }),
+});
+
+/** True when the event was caused by an Autumn API call (vs external/automatic). */
+export const isAutumnOriginatedStripeEvent = ({
+ event,
+}: {
+ event: Stripe.Event;
+}): boolean =>
+ Boolean(
+ event.request?.idempotency_key?.startsWith(
+ AUTUMN_STRIPE_IDEMPOTENCY_PREFIX,
+ ),
+ );
diff --git a/server/src/external/stripe/common/simulateTestWebhookFailure.ts b/server/src/external/stripe/common/simulateTestWebhookFailure.ts
new file mode 100644
index 0000000000..1a7b4a5d5b
--- /dev/null
+++ b/server/src/external/stripe/common/simulateTestWebhookFailure.ts
@@ -0,0 +1,92 @@
+import { tryCatch } from "@autumn/shared";
+import type Stripe from "stripe";
+import type { StripeWebhookContext } from "../webhookMiddlewares/stripeWebhookContext.js";
+
+export const TEST_WEBHOOK_FAIL_METADATA_KEY = "test_webhook_fail";
+
+type EventObjectWithMetadata = {
+ id?: string;
+ object?: string;
+ metadata?: Record | null;
+ customer?: string | { id: string } | null;
+};
+
+/** Event payloads are frozen snapshots — the live object decides recovery. */
+const retrieveLiveMetadata = async ({
+ stripeCli,
+ object,
+}: {
+ stripeCli: Stripe;
+ object: EventObjectWithMetadata;
+}): Promise | null | undefined> => {
+ if (!object.id) return object.metadata;
+
+ switch (object.object) {
+ case "checkout.session": {
+ const session = await stripeCli.checkout.sessions.retrieve(object.id);
+ return session.metadata;
+ }
+ case "subscription": {
+ const subscription = await stripeCli.subscriptions.retrieve(object.id);
+ return subscription.metadata;
+ }
+ case "invoice": {
+ const invoice = await stripeCli.invoices.retrieve(object.id);
+ return invoice.metadata;
+ }
+ default:
+ return object.metadata;
+ }
+};
+
+const retrieveLiveCustomerMetadata = async ({
+ stripeCli,
+ object,
+}: {
+ stripeCli: Stripe;
+ object: EventObjectWithMetadata;
+}): Promise | null | undefined> => {
+ const customerId =
+ typeof object.customer === "string" ? object.customer : object.customer?.id;
+ if (!customerId) return object.metadata;
+
+ const customer = await stripeCli.customers.retrieve(customerId);
+ return customer.deleted ? undefined : customer.metadata;
+};
+
+/**
+ * Non-prod failure injection: an event whose Stripe object carries
+ * `test_webhook_fail` (pass it via the billing API's `metadata` param) fails
+ * EVERY delivery until the marker is removed from the live decision object —
+ * remove it and the next delivery succeeds. Deterministic outage simulation.
+ *
+ * Marker value picks the live decision object: "customer" → the Stripe
+ * customer's metadata (toggling it emits no self-healing events for the
+ * marked object); anything else → the marked object itself.
+ */
+export const throwOnSimulatedWebhookFailure = async ({
+ ctx,
+}: {
+ ctx: StripeWebhookContext;
+}) => {
+ if (process.env.NODE_ENV === "production") return;
+
+ const event = ctx.stripeEvent;
+ const object = event.data.object as EventObjectWithMetadata;
+ const markerValue = object?.metadata?.[TEST_WEBHOOK_FAIL_METADATA_KEY];
+ if (!markerValue) return;
+
+ // Re-fetch failing open: a broken test helper must never wedge processing.
+ const { data: liveMetadata, error } = await tryCatch(
+ markerValue === "customer"
+ ? retrieveLiveCustomerMetadata({ stripeCli: ctx.stripeCli, object })
+ : retrieveLiveMetadata({ stripeCli: ctx.stripeCli, object }),
+ );
+ if (error) return;
+
+ if (liveMetadata?.[TEST_WEBHOOK_FAIL_METADATA_KEY]) {
+ throw new Error(
+ `Simulated webhook failure for ${event.type} ${event.id} — remove ${TEST_WEBHOOK_FAIL_METADATA_KEY} metadata from the live ${markerValue === "customer" ? "customer" : object.object} to let it pass`,
+ );
+ }
+};
diff --git a/server/src/external/stripe/coupons/resolvePromotionCode.ts b/server/src/external/stripe/coupons/resolvePromotionCode.ts
index e45f8eb715..16f31d5af8 100644
--- a/server/src/external/stripe/coupons/resolvePromotionCode.ts
+++ b/server/src/external/stripe/coupons/resolvePromotionCode.ts
@@ -41,7 +41,7 @@ export const resolvePromotionCode = async ({
code,
active: true,
limit: 1,
- expand: ["data.promotion.coupon"],
+ expand: ["data.promotion.coupon.applies_to"],
});
if (promos.data.length === 0) {
@@ -67,12 +67,9 @@ export const resolvePromotionCode = async ({
}
const minimumAmountsByCurrency = Object.fromEntries(
- Object.entries(
- promo.restrictions?.currency_options ?? {},
- ).map(([currency, option]) => [
- currency.toLowerCase(),
- option.minimum_amount,
- ]),
+ Object.entries(promo.restrictions?.currency_options ?? {}).map(
+ ([currency, option]) => [currency.toLowerCase(), option.minimum_amount],
+ ),
);
const hasCurrencyOptions = Object.keys(minimumAmountsByCurrency).length > 0;
diff --git a/server/src/external/stripe/handleStripeWebhookEvent.ts b/server/src/external/stripe/handleStripeWebhookEvent.ts
index bab567bf6c..e60765c84a 100644
--- a/server/src/external/stripe/handleStripeWebhookEvent.ts
+++ b/server/src/external/stripe/handleStripeWebhookEvent.ts
@@ -2,23 +2,10 @@ import * as Sentry from "@sentry/bun";
import type { Context } from "hono";
import { Stripe } from "stripe";
-import { handleStripeInvoicePaid } from "@/external/stripe/webhookHandlers/handleStripeInvoicePaid/handleStripeInvoicePaid.js";
-import { handleStripeSubscriptionUpdated } from "@/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/handleStripeSubscriptionUpdated.js";
import { unsetOrgStripeKeys } from "@/internal/orgs/orgUtils.js";
import { handleWebhookErrorSkip } from "@/utils/routerUtils/webhookErrorSkip.js";
import { getSentryTags } from "../sentry/sentryUtils.js";
-import { handleCusDiscountDeleted } from "./webhookHandlers/handleCusDiscountDeleted.js";
-import { handleStripeCustomerUpdated } from "./webhookHandlers/handleStripeCustomerUpdated.js";
-import { handleInvoiceUpdated } from "./webhookHandlers/handleInvoiceUpdated.js";
-import { handleStripeCheckoutSessionCompleted } from "./webhookHandlers/handleStripeCheckoutSessionCompleted/handleStripeCheckoutSessionCompleted.js";
-import { handleStripeCheckoutSessionExpired } from "./webhookHandlers/handleStripeCheckoutSessionExpired/handleStripeCheckoutSessionExpired.js";
-import { handleStripeInvoiceCreated } from "./webhookHandlers/handleStripeInvoiceCreated/handleStripeInvoiceCreated.js";
-import { handleStripeInvoiceFinalized } from "./webhookHandlers/handleStripeInvoiceFinalized/handleStripeInvoiceFinalized.js";
-import { handleStripeSubscriptionDeleted } from "./webhookHandlers/handleStripeSubscriptionDeleted/handleStripeSubscriptionDeleted.js";
-import { handleStripeSubscriptionCreated } from "./webhookHandlers/handleStripeSubscriptionCreated/handleStripeSubscriptionCreated.js";
-import { handleStripeTestClockReady } from "./webhookHandlers/handleStripeTestClockReady.js";
-import { handleSubscriptionScheduleCanceled } from "./webhookHandlers/handleSubScheduleCanceled.js";
-import { handleStripeSubscriptionScheduleUpdated } from "./webhookHandlers/handleStripeSubscriptionScheduleUpdated/handleStripeSubscriptionScheduleUpdated.js";
+import { runStripeWebhookHandlers } from "./runStripeWebhookHandlers.js";
import type {
StripeWebhookContext,
StripeWebhookHonoEnv,
@@ -36,78 +23,7 @@ export const handleStripeWebhookEvent = async (
const event = stripeEvent;
try {
- switch (event.type) {
- case "customer.updated":
- await handleStripeCustomerUpdated({ ctx, event });
- break;
-
- case "customer.subscription.created":
- await handleStripeSubscriptionCreated({ ctx });
- break;
-
- case "customer.subscription.updated":
- await handleStripeSubscriptionUpdated({ ctx, event });
- break;
-
- case "customer.subscription.deleted":
- await handleStripeSubscriptionDeleted({ ctx, event });
- break;
-
- case "invoice.paid":
- await handleStripeInvoicePaid({ ctx, event });
- break;
-
- case "invoice.updated":
- await handleInvoiceUpdated({
- ctx,
- event,
- });
- break;
-
- case "invoice.created":
- await handleStripeInvoiceCreated({ ctx, event });
- break;
-
- case "invoice.finalized": {
- await handleStripeInvoiceFinalized({ ctx, event });
- break;
- }
-
- case "subscription_schedule.updated": {
- await handleStripeSubscriptionScheduleUpdated({ ctx, event });
- break;
- }
-
- case "subscription_schedule.canceled": {
- const canceledSchedule = event.data.object;
- await handleSubscriptionScheduleCanceled({
- db,
- org,
- env,
- schedule: canceledSchedule,
- });
- break;
- }
-
- case "customer.discount.deleted":
- await handleCusDiscountDeleted({ ctx });
- break;
-
- case "checkout.session.completed": {
- await handleStripeCheckoutSessionCompleted({ ctx, event });
- break;
- }
-
- case "checkout.session.expired": {
- await handleStripeCheckoutSessionExpired({ ctx, event });
- break;
- }
-
- case "test_helpers.test_clock.ready": {
- await handleStripeTestClockReady({ ctx, event });
- break;
- }
- }
+ await runStripeWebhookHandlers({ ctx });
} catch (error) {
Sentry.captureException(error, {
tags: getSentryTags({
@@ -142,10 +58,14 @@ export const handleStripeWebhookEvent = async (
}
const shouldSkip = handleWebhookErrorSkip({ error, logger });
- if (!shouldSkip) {
- logger.error(`Stripe webhook error: ${error}`, { error });
+ if (shouldSkip) {
+ return c.json({ message: "Webhook received, skipped known error" }, 200);
}
- return c.json({ message: "Webhook received, internal server error" }, 200);
+
+ logger.error(`Stripe webhook error: ${error}`, { error });
+ // Rethrow so the ack middleware owns the outcome: sync events 500 (Stripe
+ // retries), early events release the idempotency lock for manual replay.
+ throw error;
}
// Note: Cache refresh is now handled by stripeWebhookRefreshMiddleware
diff --git a/server/src/external/stripe/runStripeWebhookHandlers.ts b/server/src/external/stripe/runStripeWebhookHandlers.ts
new file mode 100644
index 0000000000..24b9142bfb
--- /dev/null
+++ b/server/src/external/stripe/runStripeWebhookHandlers.ts
@@ -0,0 +1,106 @@
+import { handleStripeInvoicePaid } from "@/external/stripe/webhookHandlers/handleStripeInvoicePaid/handleStripeInvoicePaid.js";
+import { handleStripeSubscriptionUpdated } from "@/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/handleStripeSubscriptionUpdated.js";
+import { handleCusDiscountDeleted } from "./webhookHandlers/handleCusDiscountDeleted.js";
+import { handleInvoiceUpdated } from "./webhookHandlers/handleInvoiceUpdated.js";
+import { handleStripeCheckoutSessionCompleted } from "./webhookHandlers/handleStripeCheckoutSessionCompleted/handleStripeCheckoutSessionCompleted.js";
+import { handleStripeCheckoutSessionExpired } from "./webhookHandlers/handleStripeCheckoutSessionExpired/handleStripeCheckoutSessionExpired.js";
+import { handleStripeCustomerUpdated } from "./webhookHandlers/handleStripeCustomerUpdated.js";
+import { handleStripeInvoiceCreated } from "./webhookHandlers/handleStripeInvoiceCreated/handleStripeInvoiceCreated.js";
+import { handleStripeInvoiceFinalized } from "./webhookHandlers/handleStripeInvoiceFinalized/handleStripeInvoiceFinalized.js";
+import { handleStripeSubscriptionCreated } from "./webhookHandlers/handleStripeSubscriptionCreated/handleStripeSubscriptionCreated.js";
+import { handleStripeSubscriptionDeleted } from "./webhookHandlers/handleStripeSubscriptionDeleted/handleStripeSubscriptionDeleted.js";
+import { handleStripeSubscriptionScheduleUpdated } from "./webhookHandlers/handleStripeSubscriptionScheduleUpdated/handleStripeSubscriptionScheduleUpdated.js";
+import { handleStripeTestClockReady } from "./webhookHandlers/handleStripeTestClockReady.js";
+import { handleSubscriptionScheduleCanceled } from "./webhookHandlers/handleSubScheduleCanceled.js";
+import type { StripeWebhookContext } from "./webhookMiddlewares/stripeWebhookContext.js";
+
+/**
+ * Dispatches a Stripe event to its handler. Shared by the webhook route and
+ * the SQS replay worker — errors propagate to the caller.
+ */
+export const runStripeWebhookHandlers = async ({
+ ctx,
+}: {
+ ctx: StripeWebhookContext;
+}) => {
+ const { db, org, env, stripeEvent } = ctx;
+ const event = stripeEvent;
+
+ // await throwOnSimulatedWebhookFailure({ ctx });
+
+ switch (event.type) {
+ case "customer.updated":
+ await handleStripeCustomerUpdated({ ctx, event });
+ break;
+
+ case "customer.subscription.created":
+ await handleStripeSubscriptionCreated({ ctx });
+ break;
+
+ case "customer.subscription.updated":
+ await handleStripeSubscriptionUpdated({ ctx, event });
+ break;
+
+ case "customer.subscription.deleted":
+ await handleStripeSubscriptionDeleted({ ctx, event });
+ break;
+
+ case "invoice.paid":
+ await handleStripeInvoicePaid({ ctx, event });
+ break;
+
+ case "invoice.updated":
+ await handleInvoiceUpdated({
+ ctx,
+ event,
+ });
+ break;
+
+ case "invoice.created":
+ await handleStripeInvoiceCreated({ ctx, event });
+ break;
+
+ case "invoice.finalized": {
+ await handleStripeInvoiceFinalized({ ctx, event });
+ break;
+ }
+
+ case "subscription_schedule.updated": {
+ await handleStripeSubscriptionScheduleUpdated({ ctx, event });
+ break;
+ }
+
+ case "subscription_schedule.canceled": {
+ const canceledSchedule = event.data.object;
+ await handleSubscriptionScheduleCanceled({
+ db,
+ org,
+ env,
+ schedule: canceledSchedule,
+ });
+ break;
+ }
+
+ case "customer.discount.deleted":
+ await handleCusDiscountDeleted({ ctx });
+ break;
+
+ case "checkout.session.completed": {
+ await handleStripeCheckoutSessionCompleted({ ctx, event });
+ break;
+ }
+
+ case "checkout.session.expired": {
+ await handleStripeCheckoutSessionExpired({ ctx, event });
+ break;
+ }
+
+ case "test_helpers.test_clock.ready": {
+ await handleStripeTestClockReady({ ctx, event });
+ break;
+ }
+
+ default:
+ break;
+ }
+};
diff --git a/server/src/external/stripe/stripeWebhookRouter.ts b/server/src/external/stripe/stripeWebhookRouter.ts
index 38d34d3a9e..ba63494cf7 100644
--- a/server/src/external/stripe/stripeWebhookRouter.ts
+++ b/server/src/external/stripe/stripeWebhookRouter.ts
@@ -7,8 +7,8 @@ import { stripeIdempotencyMiddleware } from "./webhookMiddlewares/stripeIdempote
import { stripeLegacySeederMiddleware } from "./webhookMiddlewares/stripeLegacySeederMiddleware.js";
import { stripeSyncMiddleware } from "./webhookMiddlewares/stripeSyncMiddleware.js";
import { stripeToAutumnCustomerMiddleware } from "./webhookMiddlewares/stripeToAutumnCustomerMiddleware.js";
+import { stripeWebhookAckMiddleware } from "./webhookMiddlewares/stripeWebhookAckMiddleware.js";
import type { StripeWebhookHonoEnv } from "./webhookMiddlewares/stripeWebhookContext.js";
-import { stripeWebhookEarlyAckMiddleware } from "./webhookMiddlewares/stripeWebhookEarlyAckMiddleware.js";
import { stripeWebhookRefreshMiddleware } from "./webhookMiddlewares/stripeWebhookRefreshMiddleware.js";
export const stripeWebhookRouter = new Hono();
@@ -19,7 +19,7 @@ stripeWebhookRouter.post(
stripeLegacySeederMiddleware,
stripeToAutumnCustomerMiddleware,
stripeIdempotencyMiddleware,
- stripeWebhookEarlyAckMiddleware,
+ stripeWebhookAckMiddleware,
stripeWebhookRefreshMiddleware,
stripeSyncMiddleware,
stripeLoggerMiddleware,
@@ -33,7 +33,7 @@ stripeWebhookRouter.post(
stripeConnectSeederMiddleware,
stripeToAutumnCustomerMiddleware,
stripeIdempotencyMiddleware,
- stripeWebhookEarlyAckMiddleware,
+ stripeWebhookAckMiddleware,
stripeWebhookRefreshMiddleware,
stripeSyncMiddleware,
stripeLoggerMiddleware,
diff --git a/server/src/external/stripe/webhookHandlers/common/expireAndActivateWithTracking.ts b/server/src/external/stripe/webhookHandlers/common/expireAndActivateWithTracking.ts
index bcb1ca9232..1ea50d8639 100644
--- a/server/src/external/stripe/webhookHandlers/common/expireAndActivateWithTracking.ts
+++ b/server/src/external/stripe/webhookHandlers/common/expireAndActivateWithTracking.ts
@@ -12,12 +12,7 @@ type SubscriptionEventContext =
| StripeSubscriptionUpdatedContext
| StripeSubscriptionDeletedContext;
-/**
- * Expires a customer product and activates a free successor (scheduled or default).
- * Handles all tracking for updates and insertions.
- *
- * @returns The expired customer product (with updates applied)
- */
+/** Expires a product, activates its free successor, and records both transitions. */
export const expireAndActivateWithTracking = async ({
ctx,
eventContext,
@@ -26,7 +21,11 @@ export const expireAndActivateWithTracking = async ({
ctx: StripeWebhookContext;
eventContext: SubscriptionEventContext;
customerProduct: FullCusProduct;
-}): Promise<{ expiredCustomerProduct: FullCusProduct }> => {
+}): Promise<{
+ expiredCustomerProduct: FullCusProduct;
+ activatedCustomerProduct?: FullCusProduct;
+ insertedCustomerProduct?: FullCusProduct;
+}> => {
const { fullCustomer } = eventContext;
const { updates, activatedCustomerProduct, insertedCustomerProduct } =
@@ -60,5 +59,9 @@ export const expireAndActivateWithTracking = async ({
});
}
- return { expiredCustomerProduct };
+ return {
+ expiredCustomerProduct,
+ activatedCustomerProduct,
+ insertedCustomerProduct,
+ };
};
diff --git a/server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/tasks/resetSubscriptionPooledBalances.ts b/server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/tasks/resetSubscriptionPooledBalances.ts
index 8496b702c6..dbc1a1716f 100644
--- a/server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/tasks/resetSubscriptionPooledBalances.ts
+++ b/server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/tasks/resetSubscriptionPooledBalances.ts
@@ -5,14 +5,7 @@ import {
} from "@autumn/shared";
import type { InvoiceCreatedContext } from "@/external/stripe/webhookHandlers/handleStripeInvoiceCreated/setupInvoiceCreatedContext.js";
import type { StripeWebhookContext } from "@/external/stripe/webhookMiddlewares/stripeWebhookContext.js";
-import { resetCusEnts } from "@/internal/balances/utils/sql/client.js";
-import { applyResetResults } from "@/internal/customers/actions/resetCustomerEntitlements/applyResetResults.js";
-import {
- type ProcessResetResult,
- processReset,
-} from "@/internal/customers/actions/resetCustomerEntitlements/processReset.js";
-import { processResetResultToResetCusEntParam } from "@/internal/customers/actions/resetCustomerEntitlements/processResetResultToResetCusEntParam.js";
-import { invalidateCachedFullSubject } from "@/internal/customers/cache/fullSubject/index.js";
+import { resetPooledBalances } from "@/internal/billing/v2/pooledBalances/execute/resetPooledBalances.js";
export const resetSubscriptionPooledBalances = async ({
ctx,
@@ -26,9 +19,7 @@ export const resetSubscriptionPooledBalances = async ({
const pooledCustomerEntitlements =
eventContext.fullCustomer.pooled_customer_entitlements ?? [];
- const invoicePeriodEndMs = secondsToMs(
- eventContext.stripeInvoice.period_end,
- );
+ const invoicePeriodEndMs = secondsToMs(eventContext.stripeInvoice.period_end);
const resettablePooledCustomerEntitlements =
pooledCustomerEntitlements.filter(
(customerEntitlement) =>
@@ -44,42 +35,10 @@ export const resetSubscriptionPooledBalances = async ({
if (resettablePooledCustomerEntitlements.length === 0) return;
- const computed: Array<{
- cusEntId: string;
- result: ProcessResetResult;
- }> = [];
-
- for (const customerEntitlement of resettablePooledCustomerEntitlements) {
- const result = await processReset({
- ctx,
- cusEnt: { ...customerEntitlement, customer_product: null },
- });
- if (result) computed.push({ cusEntId: customerEntitlement.id, result });
- }
-
- if (computed.length === 0) return;
-
- const resets = computed.map(({ cusEntId, result }) =>
- processResetResultToResetCusEntParam({
- customerEntitlementId: cusEntId,
- result,
- }),
- );
- const { applied, skipped } = await resetCusEnts({ ctx, resets });
-
- await applyResetResults({
+ await resetPooledBalances({
ctx,
- fullCus: eventContext.fullCustomer,
- computed,
- skipped,
+ fullCustomer: eventContext.fullCustomer,
+ pooledCustomerEntitlements: resettablePooledCustomerEntitlements,
+ source: "invoice-created-pooled-balance-reset",
});
-
- if (Object.keys(applied).length > 0) {
- await invalidateCachedFullSubject({
- ctx,
- customerId:
- eventContext.fullCustomer.id ?? eventContext.fullCustomer.internal_id,
- source: "invoice-created-pooled-balance-reset",
- });
- }
};
diff --git a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionDeleted/tasks/expireAndActivateCustomerProducts.ts b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionDeleted/tasks/expireAndActivateCustomerProducts.ts
index db09f57693..91c751dcc4 100644
--- a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionDeleted/tasks/expireAndActivateCustomerProducts.ts
+++ b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionDeleted/tasks/expireAndActivateCustomerProducts.ts
@@ -6,6 +6,7 @@ import {
isCustomerProductScheduled,
} from "@autumn/shared";
import type { StripeWebhookContext } from "@/external/stripe/webhookMiddlewares/stripeWebhookContext";
+import { applyPooledBalanceCustomerProductTransitions } from "@/internal/billing/v2/pooledBalances/execute/applyPooledBalanceCustomerProductTransitions";
import { customerProductActions } from "@/internal/customers/cusProducts/actions";
import { deleteScheduledCustomerProduct } from "@/internal/customers/cusProducts/actions/deleteScheduledCustomerProduct";
import {
@@ -30,6 +31,8 @@ export const expireAndActivateCustomerProducts = async ({
);
const expiredCustomerProducts: FullCusProduct[] = [];
+ const outgoingCustomerProducts: FullCusProduct[] = [];
+ const incomingCustomerProducts: FullCusProduct[] = [];
const liveCustomerProducts = customerProducts.filter(
(customerProduct) => !isCustomerProductScheduled(customerProduct),
);
@@ -43,13 +46,24 @@ export const expireAndActivateCustomerProducts = async ({
if (!onStripeSubscription) continue;
// 2. Expire and activate free successor (with tracking)
- const { expiredCustomerProduct } = await expireAndActivateWithTracking({
+ const {
+ expiredCustomerProduct,
+ activatedCustomerProduct,
+ insertedCustomerProduct,
+ } = await expireAndActivateWithTracking({
ctx,
eventContext,
customerProduct,
});
expiredCustomerProducts.push(expiredCustomerProduct);
+ outgoingCustomerProducts.push(customerProduct);
+ if (activatedCustomerProduct) {
+ incomingCustomerProducts.push(activatedCustomerProduct);
+ }
+ if (insertedCustomerProduct) {
+ incomingCustomerProducts.push(insertedCustomerProduct);
+ }
// 3. Delete paid scheduled customer product for this group if it exists...
const scheduledCustomerProduct = findMainScheduledCustomerProductByGroup({
@@ -75,6 +89,14 @@ export const expireAndActivateCustomerProducts = async ({
}
}
+ await applyPooledBalanceCustomerProductTransitions({
+ ctx,
+ fullCustomer,
+ outgoingCustomerProducts,
+ incomingCustomerProducts,
+ now: eventContext.nowMs,
+ });
+
// invoice.created needs the expired snapshots for final usage billing.
await customerProductActions.expiredCache.set({
stripeSubscriptionId: stripeSubscription.id,
diff --git a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/handleStripeSubscriptionUpdated.ts b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/handleStripeSubscriptionUpdated.ts
index f3e63edf3a..33b0289676 100644
--- a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/handleStripeSubscriptionUpdated.ts
+++ b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/handleStripeSubscriptionUpdated.ts
@@ -4,6 +4,7 @@ import { syncAutumnSubscription } from "@/external/stripe/webhookHandlers/handle
import type { StripeWebhookContext } from "../../webhookMiddlewares/stripeWebhookContext.js";
import { emitBillingChangeWebhook, logCustomerProductUpdates } from "../common";
import { setupStripeSubscriptionUpdatedContext } from "./setupStripeSubscriptionUpdatedContext.js";
+import { applyPooledBalanceTransitions } from "./tasks/applyPooledBalanceTransitions";
import { autoSyncUpdatedSubscription } from "./tasks/autoSyncUpdatedSubscription.js";
import { handleCancelOnPastDue } from "./tasks/handleCancelOnPastDue.js";
import { handleIgnorePastDue } from "./tasks/handleIgnorePastDue.js";
@@ -83,6 +84,10 @@ export const handleStripeSubscriptionUpdated = async ({
ctx,
subscriptionUpdatedContext,
});
+ await applyPooledBalanceTransitions({
+ ctx,
+ eventContext: subscriptionUpdatedContext,
+ });
// 7. Log all customer product updates
logCustomerProductUpdates({
diff --git a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/applyPooledBalanceTransitions.ts b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/applyPooledBalanceTransitions.ts
new file mode 100644
index 0000000000..5a2328e8d2
--- /dev/null
+++ b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/applyPooledBalanceTransitions.ts
@@ -0,0 +1,48 @@
+import {
+ CusProductStatus,
+ customerProductHasActiveStatus,
+ type FullCusProduct,
+} from "@autumn/shared";
+import { applyPooledBalanceCustomerProductTransitions } from "@/internal/billing/v2/pooledBalances/execute/applyPooledBalanceCustomerProductTransitions";
+import type { StripeWebhookContext } from "../../../webhookMiddlewares/stripeWebhookContext";
+import type { StripeSubscriptionUpdatedContext } from "../stripeSubscriptionUpdatedContext";
+
+export const applyPooledBalanceTransitions = async ({
+ ctx,
+ eventContext,
+}: {
+ ctx: StripeWebhookContext;
+ eventContext: StripeSubscriptionUpdatedContext;
+}) => {
+ const outgoingCustomerProducts = new Map();
+ const incomingCustomerProducts = new Map();
+
+ for (const {
+ customerProduct,
+ updates,
+ } of eventContext.updatedCustomerProducts) {
+ if (updates.status === CusProductStatus.Expired) {
+ outgoingCustomerProducts.set(customerProduct.id, customerProduct);
+ }
+ if (updates.status === CusProductStatus.Active) {
+ incomingCustomerProducts.set(customerProduct.id, {
+ ...customerProduct,
+ ...updates,
+ } as FullCusProduct);
+ }
+ }
+
+ for (const customerProduct of eventContext.insertedCustomerProducts) {
+ if (customerProductHasActiveStatus(customerProduct)) {
+ incomingCustomerProducts.set(customerProduct.id, customerProduct);
+ }
+ }
+
+ await applyPooledBalanceCustomerProductTransitions({
+ ctx,
+ fullCustomer: eventContext.fullCustomer,
+ outgoingCustomerProducts: Array.from(outgoingCustomerProducts.values()),
+ incomingCustomerProducts: Array.from(incomingCustomerProducts.values()),
+ now: eventContext.nowMs,
+ });
+};
diff --git a/server/src/external/stripe/webhookMiddlewares/classifyStripeWebhookAckMode.ts b/server/src/external/stripe/webhookMiddlewares/classifyStripeWebhookAckMode.ts
new file mode 100644
index 0000000000..55478b04b1
--- /dev/null
+++ b/server/src/external/stripe/webhookMiddlewares/classifyStripeWebhookAckMode.ts
@@ -0,0 +1,69 @@
+import type Stripe from "stripe";
+import { isAutumnOriginatedStripeEvent } from "@/external/stripe/common/autumnStripeIdempotency.js";
+import { isAutumnManagedSubscriptionMetadata } from "@/internal/billing/v2/providers/stripe/utils/common/autumnStripeMetadata.js";
+
+export type StripeWebhookAckMode = "early" | "sync";
+
+/**
+ * Decides whether a Stripe webhook is acked before processing ("early") or only
+ * after processing succeeds ("sync", so a failure 500s and Stripe retries).
+ * Sync is reserved for events whose handler is load-bearing: external changes
+ * Autumn didn't cause, plus deferred-checkout / period-boundary work that runs
+ * ONLY in the webhook. Early-ack keeps Autumn-triggered events from blocking
+ * the billing actions that emitted them.
+ */
+export const classifyStripeWebhookAckMode = ({
+ event,
+ now = Date.now(),
+}: {
+ event: Stripe.Event | undefined;
+ now?: number;
+}): StripeWebhookAckMode => {
+ if (!event) return "early";
+
+ switch (event.type) {
+ // Sole executor of paid-product activation / abandoned-checkout cleanup.
+ case "checkout.session.completed":
+ case "checkout.session.expired":
+ return "sync";
+
+ // Pure external->Autumn mirror (name/email); cheap and always external.
+ case "customer.updated":
+ return "sync";
+
+ // Deferred billing plan execution (invoice-mode checkout, 3DS completion).
+ case "invoice.paid": {
+ const invoice = event.data.object;
+ return invoice.metadata?.autumn_metadata_id ? "sync" : "early";
+ }
+
+ // Vercel marketplace submission happens only in this webhook.
+ case "invoice.finalized": {
+ const invoice = event.data.object;
+ return invoice.metadata?.vercel_installation_id ? "sync" : "early";
+ }
+
+ // Cycle invoices own period-boundary balance resets + arrear billing.
+ case "invoice.created": {
+ const invoice = event.data.object;
+ return invoice.billing_reason === "subscription_cycle" ? "sync" : "early";
+ }
+
+ case "customer.subscription.created":
+ case "customer.subscription.updated":
+ case "customer.subscription.deleted": {
+ if (isAutumnOriginatedStripeEvent({ event })) return "early";
+
+ const subscription = event.data.object;
+ const { skip } = isAutumnManagedSubscriptionMetadata({
+ metadata: subscription.metadata,
+ now,
+ requireRecent: event.type !== "customer.subscription.created",
+ });
+ return skip ? "early" : "sync";
+ }
+
+ default:
+ return "early";
+ }
+};
diff --git a/server/src/external/stripe/webhookMiddlewares/stripeIdempotencyMiddleware.ts b/server/src/external/stripe/webhookMiddlewares/stripeIdempotencyMiddleware.ts
index 3c203637ba..2781c93122 100644
--- a/server/src/external/stripe/webhookMiddlewares/stripeIdempotencyMiddleware.ts
+++ b/server/src/external/stripe/webhookMiddlewares/stripeIdempotencyMiddleware.ts
@@ -1,16 +1,72 @@
+import type { AppEnv } from "@autumn/shared";
import { tryCatch } from "@autumn/shared";
import type { Context, Next } from "hono";
import { redis } from "@/external/redis/initRedis";
-import type { StripeWebhookHonoEnv } from "./stripeWebhookContext";
+import { classifyStripeWebhookAckMode } from "./classifyStripeWebhookAckMode.js";
+import type { StripeWebhookHonoEnv } from "./stripeWebhookContext.js";
-const IDEMPOTENCY_TTL_MS = 5 * 60 * 1000; // 5 minutes
+const PROCESSING_TTL_MS = 5 * 60 * 1000;
+const COMPLETED_TTL_MS = 24 * 60 * 60 * 1000;
+const PROCESSING = "processing";
+const COMPLETED = "completed";
+
+export const buildStripeWebhookEventKey = ({
+ orgId,
+ env,
+ eventId,
+}: {
+ orgId: string;
+ env: AppEnv;
+ eventId: string;
+}) => `stripe:webhook:${orgId}:${env}:${eventId}`;
+
+export type StripeWebhookClaimResult =
+ | "claimed"
+ | "duplicate_completed"
+ | "in_flight"
+ | "unavailable";
+
+/** Atomically claims an event for processing. "unavailable" = Redis down (fail open). */
+export const claimStripeWebhookEvent = async ({
+ eventKey,
+}: {
+ eventKey: string;
+}): Promise => {
+ if (redis.status !== "ready") return "unavailable";
+
+ const { data: result, error } = await tryCatch(
+ redis.set(eventKey, PROCESSING, "PX", PROCESSING_TTL_MS, "NX"),
+ );
+ if (error) return "unavailable";
+ if (result !== null) return "claimed";
+
+ const { data: existing } = await tryCatch(redis.get(eventKey));
+ return existing === COMPLETED ? "duplicate_completed" : "in_flight";
+};
+
+export const completeStripeWebhookEvent = async ({
+ eventKey,
+}: {
+ eventKey: string;
+}) => {
+ await tryCatch(redis.set(eventKey, COMPLETED, "PX", COMPLETED_TTL_MS));
+};
+
+export const releaseStripeWebhookEvent = async ({
+ eventKey,
+}: {
+ eventKey: string;
+}) => {
+ await tryCatch(redis.del(eventKey));
+};
/**
- * Middleware that prevents duplicate processing of Stripe webhook events.
- * Uses Redis SET NX PX to atomically check and set event ID with expiry,
- * ensuring only one instance processes each event even with concurrent deliveries.
+ * Two-state webhook idempotency: a processing lock is taken before handling,
+ * but the event only counts as a duplicate once marked COMPLETED (via the
+ * ctx.webhookIdempotency hooks). A failed run deletes the lock so Stripe's
+ * retry of the same event id reprocesses instead of being dropped.
*
- * If Redis is unavailable or errors, the middleware allows the request through (fail-open).
+ * If Redis is unavailable or errors, the request is allowed through (fail-open).
*/
export const stripeIdempotencyMiddleware = async (
c: Context,
@@ -19,44 +75,47 @@ export const stripeIdempotencyMiddleware = async (
const ctx = c.get("ctx");
const { stripeEvent, org, env } = ctx;
- const idempotencyKey = `stripe:webhook:${org.id}:${env}:${stripeEvent.id}`;
+ ctx.webhookAckMode = classifyStripeWebhookAckMode({ event: stripeEvent });
- // Fail open if Redis not ready
- if (redis.status !== "ready") {
- await next();
- return;
- }
+ const eventKey = buildStripeWebhookEventKey({
+ orgId: org.id,
+ env,
+ eventId: stripeEvent.id,
+ });
- // Atomically try to set the key with expiry
- // NX = only set if key doesn't exist, PX = set expiry in milliseconds
- // Returns "OK" if set, null if key already exists
- const { data: result, error } = await tryCatch(
- redis.set(
- idempotencyKey,
- Date.now().toString(),
- "PX",
- IDEMPOTENCY_TTL_MS,
- "NX",
- ),
- );
+ const claim = await claimStripeWebhookEvent({ eventKey });
- if (error) {
- // Redis error - fail open
+ if (claim === "unavailable") {
+ // Redis down or errored - fail open
ctx.logger.warn(
- `[stripeIdempotencyMiddleware] Redis error, allowing through: ${error}`,
+ `[stripeIdempotencyMiddleware] Redis unavailable, allowing through: ${stripeEvent.id}`,
);
await next();
return;
}
- if (result === null) {
- // Key already exists - duplicate event
+ if (
+ claim === "duplicate_completed" ||
+ (claim === "in_flight" && ctx.webhookAckMode === "early")
+ ) {
ctx.logger.info(
`[stripeIdempotencyMiddleware] Duplicate webhook event detected, skipping: ${stripeEvent.id}`,
);
return c.json({ received: true, duplicate: true }, 200);
}
- // Lock acquired ("OK"), proceed with processing
+ if (claim === "in_flight") {
+ // Sync event still in flight — 500 keeps Stripe retrying until completed.
+ ctx.logger.info(
+ `[stripeIdempotencyMiddleware] Sync webhook event in flight, asking Stripe to retry: ${stripeEvent.id}`,
+ );
+ return c.json({ received: false, in_flight: true }, 500);
+ }
+
+ ctx.webhookIdempotency = {
+ markCompleted: () => completeStripeWebhookEvent({ eventKey }),
+ release: () => releaseStripeWebhookEvent({ eventKey }),
+ };
+
await next();
};
diff --git a/server/src/external/stripe/webhookMiddlewares/stripeSyncMiddleware.ts b/server/src/external/stripe/webhookMiddlewares/stripeSyncMiddleware.ts
index 410de04ca8..720938ef06 100644
--- a/server/src/external/stripe/webhookMiddlewares/stripeSyncMiddleware.ts
+++ b/server/src/external/stripe/webhookMiddlewares/stripeSyncMiddleware.ts
@@ -1,5 +1,6 @@
import { isSyncableEvent, processStripeSyncEvent } from "@autumn/stripe-sync";
import type { Context, Next } from "hono";
+import { orgToAccountId } from "@/external/connect/connectUtils.js";
import { isStripeSyncEnabled } from "@/internal/misc/stripeSync/stripeSyncStore.js";
import type {
StripeWebhookContext,
@@ -7,16 +8,15 @@ import type {
} from "./stripeWebhookContext.js";
/**
- * Post-handler middleware that syncs Stripe events to the sync DB.
- * Fire-and-forget -- errors are caught and logged, never propagated.
+ * Mirrors a Stripe event into the sync DB. Fire-and-forget — errors are
+ * caught and logged, never propagated. Shared by the webhook route and the
+ * SQS replay worker.
*/
-export const stripeSyncMiddleware = async (
- c: Context,
- next: Next,
-) => {
- await next();
-
- const ctx = c.get("ctx") as StripeWebhookContext;
+export const syncStripeEventToSyncDb = ({
+ ctx,
+}: {
+ ctx: StripeWebhookContext;
+}) => {
const { logger, org, stripeEvent } = ctx;
if (!org || !stripeEvent) return;
@@ -29,7 +29,10 @@ export const stripeSyncMiddleware = async (
if (!isSyncableEvent({ eventType: stripeEvent.type })) return;
try {
- const stripeAccountId = stripeEvent.account ?? undefined;
+ // Events from account-registered webhooks carry no `account` — resolve
+ // from org connect config so sync rows are never left tenant-unstamped.
+ const stripeAccountId =
+ stripeEvent.account ?? orgToAccountId({ org, env: ctx.env });
void processStripeSyncEvent({
event: stripeEvent,
@@ -52,3 +55,16 @@ export const stripeSyncMiddleware = async (
logger.error(`Stripe sync middleware error: ${error}`);
}
};
+
+/**
+ * Post-handler middleware that syncs Stripe events to the sync DB.
+ */
+export const stripeSyncMiddleware = async (
+ c: Context,
+ next: Next,
+) => {
+ await next();
+
+ const ctx = c.get("ctx") as StripeWebhookContext;
+ syncStripeEventToSyncDb({ ctx });
+};
diff --git a/server/src/external/stripe/webhookMiddlewares/stripeToAutumnCustomerMiddleware.ts b/server/src/external/stripe/webhookMiddlewares/stripeToAutumnCustomerMiddleware.ts
index 6db1e75525..01c4b23076 100644
--- a/server/src/external/stripe/webhookMiddlewares/stripeToAutumnCustomerMiddleware.ts
+++ b/server/src/external/stripe/webhookMiddlewares/stripeToAutumnCustomerMiddleware.ts
@@ -55,30 +55,44 @@ const getAutumnCustomerId = async ({ ctx }: { ctx: StripeWebhookContext }) => {
ctx.fullCustomer = fullCustomer;
};
-export const stripeToAutumnCustomerMiddleware = async (
- c: Context,
- next: Next,
-) => {
- const ctx = c.get("ctx") as StripeWebhookContext;
+/**
+ * Resolves the event's Autumn customer onto ctx.fullCustomer and returns a
+ * ctx routed to that customer's Redis. Shared by the webhook route and the
+ * SQS replay worker.
+ */
+export const attachStripeEventCustomer = async ({
+ ctx,
+}: {
+ ctx: StripeWebhookContext;
+}): Promise => {
await getAutumnCustomerId({ ctx });
const customerId =
ctx.fullCustomer?.id || ctx.fullCustomer?.internal_id || undefined;
- if (customerId) {
- const { ctx: routedCtx } = getCtxWithCustomerRedis({
- ctx: {
- ...ctx,
- customerId,
- rolloutSnapshot: computeRolloutSnapshot({
- orgId: ctx.org.id,
- customerId,
- }),
- },
+ if (!customerId) return ctx;
+
+ const { ctx: routedCtx } = getCtxWithCustomerRedis({
+ ctx: {
+ ...ctx,
customerId,
- });
- c.set("ctx", routedCtx);
- }
+ rolloutSnapshot: computeRolloutSnapshot({
+ orgId: ctx.org.id,
+ customerId,
+ }),
+ },
+ customerId,
+ });
+ return routedCtx;
+};
+
+export const stripeToAutumnCustomerMiddleware = async (
+ c: Context,
+ next: Next,
+) => {
+ const ctx = c.get("ctx") as StripeWebhookContext;
+ const routedCtx = await attachStripeEventCustomer({ ctx });
+ if (routedCtx !== ctx) c.set("ctx", routedCtx);
await next();
};
diff --git a/server/src/external/stripe/webhookMiddlewares/stripeWebhookAckMiddleware.ts b/server/src/external/stripe/webhookMiddlewares/stripeWebhookAckMiddleware.ts
new file mode 100644
index 0000000000..b02ec2d62a
--- /dev/null
+++ b/server/src/external/stripe/webhookMiddlewares/stripeWebhookAckMiddleware.ts
@@ -0,0 +1,114 @@
+import { context, SpanStatusCode, trace } from "@opentelemetry/api";
+import type { Context, Next } from "hono";
+import { enqueueStripeWebhookReplay } from "../webhookReplay/enqueueStripeWebhookReplay.js";
+import { classifyStripeWebhookAckMode } from "./classifyStripeWebhookAckMode.js";
+import type { StripeWebhookHonoEnv } from "./stripeWebhookContext.js";
+
+const tracer = trace.getTracer("autumn-stripe-webhook");
+
+const getWaitUntil = (c: Context) => {
+ try {
+ return c.executionCtx.waitUntil.bind(c.executionCtx);
+ } catch {
+ return undefined;
+ }
+};
+
+/**
+ * Ack policy: "early" events are acked 200 before processing (Stripe blocks
+ * its own API calls on webhook delivery, so Autumn-triggered events must not
+ * wait). "sync" events are acked only after processing succeeds — a failure
+ * releases the idempotency lock and 500s so Stripe redelivers.
+ */
+export const stripeWebhookAckMiddleware = async (
+ c: Context,
+ next: Next,
+) => {
+ const ctx = c.get("ctx");
+ const ackMode =
+ ctx.webhookAckMode ??
+ classifyStripeWebhookAckMode({ event: ctx.stripeEvent });
+ const idempotency = ctx.webhookIdempotency;
+
+ if (ackMode === "sync") {
+ try {
+ await next();
+ } catch (error) {
+ ctx.logger.error(`Stripe webhook sync processing failed: ${error}`, {
+ error,
+ });
+ await idempotency?.release();
+ return c.json(
+ { message: "Webhook processing failed, Stripe will retry" },
+ 500,
+ );
+ }
+
+ // A throwing handler resolves next() with c.error set (app onError owns
+ // the response), so failure must be detected here, not via catch.
+ if (c.error) {
+ await idempotency?.release();
+ return;
+ }
+
+ await idempotency?.markCompleted();
+ return;
+ }
+
+ // Start a span that survives past the early-ack response. Parented to the
+ // still-active root HTTP span; ended in finally(...) when deferred work
+ // completes so traceEnrichMiddleware + child spans land on a live span.
+ const deferredSpan = tracer.startSpan("stripe.webhook.deferred");
+ const ctxWithSpan = trace.setSpan(context.active(), deferredSpan);
+
+ const runWebhook = () =>
+ context.with(ctxWithSpan, () =>
+ Promise.resolve()
+ .then(next)
+ .then(() => {
+ if (c.error) throw c.error;
+ return idempotency?.markCompleted();
+ })
+ .catch(async (error) => {
+ deferredSpan.recordException(error);
+ deferredSpan.setStatus({ code: SpanStatusCode.ERROR });
+ await idempotency?.release();
+
+ const queued = await enqueueStripeWebhookReplay({
+ ctx,
+ failureReason:
+ error instanceof Error ? error.message : String(error),
+ });
+ ctx.logger.error(
+ `[stripeWebhookAck] Early-acked Stripe webhook FAILED after 200 (${ctx.stripeEvent?.type}), ${queued ? "queued for replay" : "NOT queued for replay"}`,
+ {
+ error,
+ data: {
+ eventId: ctx.stripeEvent?.id,
+ eventType: ctx.stripeEvent?.type,
+ queuedForReplay: queued,
+ },
+ },
+ );
+ })
+ .finally(() => deferredSpan.end()),
+ );
+
+ const waitUntil = getWaitUntil(c);
+ if (waitUntil) {
+ let webhookRun: Promise | undefined;
+ try {
+ webhookRun = runWebhook();
+ waitUntil(webhookRun);
+ } catch (error) {
+ ctx.logger.error(`Stripe webhook waitUntil failed: ${error}`, { error });
+ // The chain settles the claim + span itself once started; only a
+ // failure BEFORE start needs rescheduling so the event isn't lost.
+ if (!webhookRun) setImmediate(() => void runWebhook());
+ }
+ } else {
+ setImmediate(() => void runWebhook());
+ }
+
+ return c.json({ received: true }, 200);
+};
diff --git a/server/src/external/stripe/webhookMiddlewares/stripeWebhookContext.ts b/server/src/external/stripe/webhookMiddlewares/stripeWebhookContext.ts
index 7b6abf6aa8..61f1d774e7 100644
--- a/server/src/external/stripe/webhookMiddlewares/stripeWebhookContext.ts
+++ b/server/src/external/stripe/webhookMiddlewares/stripeWebhookContext.ts
@@ -1,11 +1,20 @@
import type { Customer, FullCustomer } from "@autumn/shared";
import type Stripe from "stripe";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
+import type { StripeWebhookAckMode } from "./classifyStripeWebhookAckMode.js";
+
+/** Never-throwing Redis hooks installed by the idempotency middleware. */
+export type StripeWebhookIdempotencyHooks = {
+ markCompleted: () => Promise;
+ release: () => Promise;
+};
export interface StripeWebhookContext extends AutumnContext {
stripeEvent: Stripe.Event;
stripeCli: Stripe;
fullCustomer?: FullCustomer;
+ webhookAckMode?: StripeWebhookAckMode;
+ webhookIdempotency?: StripeWebhookIdempotencyHooks;
}
export type StripeWebhookHonoEnv = {
diff --git a/server/src/external/stripe/webhookMiddlewares/stripeWebhookEarlyAckMiddleware.ts b/server/src/external/stripe/webhookMiddlewares/stripeWebhookEarlyAckMiddleware.ts
deleted file mode 100644
index fb7a68fc0e..0000000000
--- a/server/src/external/stripe/webhookMiddlewares/stripeWebhookEarlyAckMiddleware.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { SpanStatusCode, context, trace } from "@opentelemetry/api";
-import type { Context, Next } from "hono";
-import type { StripeWebhookHonoEnv } from "./stripeWebhookContext";
-
-const tracer = trace.getTracer("autumn-stripe-webhook");
-
-const getWaitUntil = (c: Context) => {
- try {
- return c.executionCtx.waitUntil.bind(c.executionCtx);
- } catch {
- return undefined;
- }
-};
-
-export const stripeWebhookEarlyAckMiddleware = async (
- c: Context,
- next: Next,
-) => {
- const ctx = c.get("ctx");
-
- // Start a span that survives past the early-ack response. Parented to the
- // still-active root HTTP span; ended in finally(...) when deferred work
- // completes so traceEnrichMiddleware + child spans land on a live span.
- const deferredSpan = tracer.startSpan("stripe.webhook.deferred");
- const ctxWithSpan = trace.setSpan(context.active(), deferredSpan);
-
- const runWebhook = () =>
- context.with(ctxWithSpan, () =>
- Promise.resolve()
- .then(next)
- .catch((error) => {
- deferredSpan.recordException(error);
- deferredSpan.setStatus({ code: SpanStatusCode.ERROR });
- ctx.logger.error(
- `Stripe webhook background processing failed: ${error}`,
- { error },
- );
- })
- .finally(() => deferredSpan.end()),
- );
-
- const waitUntil = getWaitUntil(c);
- if (waitUntil) {
- try {
- waitUntil(runWebhook());
- } catch (error) {
- deferredSpan.end();
- ctx.logger.error(`Stripe webhook waitUntil failed: ${error}`, { error });
- }
- } else {
- setImmediate(() => void runWebhook());
- }
-
- return c.json({ received: true }, 200);
-};
diff --git a/server/src/external/stripe/webhookReplay/enqueueStripeWebhookReplay.ts b/server/src/external/stripe/webhookReplay/enqueueStripeWebhookReplay.ts
new file mode 100644
index 0000000000..5595f2145f
--- /dev/null
+++ b/server/src/external/stripe/webhookReplay/enqueueStripeWebhookReplay.ts
@@ -0,0 +1,54 @@
+import { tryCatch } from "@autumn/shared";
+import { JobName } from "@/queue/JobName.js";
+import { addTaskToQueue } from "@/queue/queueUtils.js";
+import type { StripeWebhookContext } from "../webhookMiddlewares/stripeWebhookContext.js";
+
+export const getStripeWebhookReplayQueueUrl = () =>
+ process.env.STRIPE_WEBHOOK_SQS_QUEUE_URL;
+
+/**
+ * Queues a failed early-acked webhook for replay. Never throws — the caller
+ * is a background failure path and enqueue is best-effort.
+ */
+export const enqueueStripeWebhookReplay = async ({
+ ctx,
+ failureReason,
+}: {
+ ctx: StripeWebhookContext;
+ failureReason: string;
+}): Promise => {
+ const { org, env, stripeEvent, logger } = ctx;
+
+ const queueUrl = getStripeWebhookReplayQueueUrl();
+ if (!org || !stripeEvent) return false;
+ if (!queueUrl) {
+ logger.warn(
+ `[stripeWebhookReplay] STRIPE_WEBHOOK_SQS_QUEUE_URL not set, cannot queue failed event ${stripeEvent.id} for replay`,
+ );
+ return false;
+ }
+
+ const { error } = await tryCatch(
+ addTaskToQueue({
+ jobName: JobName.StripeWebhookReplay,
+ queueUrl,
+ payload: {
+ orgId: org.id,
+ env,
+ stripeEvent,
+ failedAt: Date.now(),
+ failureReason,
+ },
+ }),
+ );
+
+ if (error) {
+ logger.error(
+ `[stripeWebhookReplay] Failed to queue event ${stripeEvent.id} for replay: ${error}`,
+ { error },
+ );
+ return false;
+ }
+
+ return true;
+};
diff --git a/server/src/external/stripe/webhookReplay/runStripeWebhookReplay.ts b/server/src/external/stripe/webhookReplay/runStripeWebhookReplay.ts
new file mode 100644
index 0000000000..bf91368935
--- /dev/null
+++ b/server/src/external/stripe/webhookReplay/runStripeWebhookReplay.ts
@@ -0,0 +1,99 @@
+import { AuthType, tryCatch } from "@autumn/shared";
+import type Stripe from "stripe";
+import { createStripeCli } from "@/external/connect/createStripeCli.js";
+import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
+import { deleteCachedFullCustomer } from "@/internal/customers/cusUtils/fullCustomerCacheUtils/deleteCachedFullCustomer.js";
+import { runStripeWebhookHandlers } from "../runStripeWebhookHandlers.js";
+import {
+ buildStripeWebhookEventKey,
+ claimStripeWebhookEvent,
+ completeStripeWebhookEvent,
+ releaseStripeWebhookEvent,
+} from "../webhookMiddlewares/stripeIdempotencyMiddleware.js";
+import { syncStripeEventToSyncDb } from "../webhookMiddlewares/stripeSyncMiddleware.js";
+import { attachStripeEventCustomer } from "../webhookMiddlewares/stripeToAutumnCustomerMiddleware.js";
+import type { StripeWebhookContext } from "../webhookMiddlewares/stripeWebhookContext.js";
+
+export type StripeWebhookReplayPayload = {
+ orgId: string;
+ env: AutumnContext["env"];
+ stripeEvent: Stripe.Event;
+ failedAt: number;
+ failureReason: string;
+};
+
+/** Thrown when another instance holds the event lock; retryable via SQS. */
+export class StripeWebhookReplayInFlightError extends Error {
+ constructor(eventId: string) {
+ super(`Stripe webhook replay in flight for event ${eventId}`);
+ this.name = "StripeWebhookReplayInFlightError";
+ }
+}
+
+/**
+ * Worker entry: replays a failed early-acked Stripe webhook through the same
+ * pipeline the route runs (customer resolution -> handlers -> cache refresh
+ * -> sync mirror), guarded by the same Redis idempotency claim.
+ */
+export const runStripeWebhookReplay = async ({
+ ctx,
+ payload,
+}: {
+ ctx: AutumnContext;
+ payload: StripeWebhookReplayPayload;
+}) => {
+ const { stripeEvent } = payload;
+ const { logger } = ctx;
+
+ const webhookCtx: StripeWebhookContext = {
+ ...ctx,
+ authType: AuthType.Stripe,
+ stripeEvent,
+ stripeCli: createStripeCli({ org: ctx.org, env: ctx.env }),
+ };
+
+ const routedCtx = await attachStripeEventCustomer({ ctx: webhookCtx });
+
+ const eventKey = buildStripeWebhookEventKey({
+ orgId: ctx.org.id,
+ env: ctx.env,
+ eventId: stripeEvent.id,
+ });
+ const claim = await claimStripeWebhookEvent({ eventKey });
+
+ if (claim === "duplicate_completed") {
+ logger.info(
+ `[stripeWebhookReplay] Event ${stripeEvent.id} already completed elsewhere, skipping replay`,
+ );
+ return;
+ }
+
+ if (claim === "in_flight") {
+ throw new StripeWebhookReplayInFlightError(stripeEvent.id);
+ }
+
+ logger.info(
+ `[stripeWebhookReplay] Replaying ${stripeEvent.type} (${stripeEvent.id}), originally failed at ${new Date(payload.failedAt).toISOString()}: ${payload.failureReason}`,
+ );
+
+ try {
+ await runStripeWebhookHandlers({ ctx: routedCtx });
+ } catch (error) {
+ if (claim === "claimed") await releaseStripeWebhookEvent({ eventKey });
+ throw error;
+ }
+
+ if (claim === "claimed") await completeStripeWebhookEvent({ eventKey });
+
+ // Post-processing mirrors the route's refresh + sync middlewares (best-effort).
+ if (routedCtx.fullCustomer?.id) {
+ await tryCatch(
+ deleteCachedFullCustomer({
+ customerId: routedCtx.fullCustomer.id,
+ ctx: routedCtx,
+ source: `stripeWebhookReplay: ${stripeEvent.type}`,
+ }),
+ );
+ }
+ syncStripeEventToSyncDb({ ctx: routedCtx });
+};
diff --git a/server/src/honoMiddlewares/baseMiddleware.ts b/server/src/honoMiddlewares/baseMiddleware.ts
index e40288a165..dc91a0b4f1 100644
--- a/server/src/honoMiddlewares/baseMiddleware.ts
+++ b/server/src/honoMiddlewares/baseMiddleware.ts
@@ -124,6 +124,7 @@ export const baseMiddleware = async (c: Context, next: Next) => {
userId: undefined,
customerId,
entityId,
+ requestBody: body,
authType: AuthType.Unknown,
env: AppEnv.Sandbox, // maybe use app_env headers
scopes: [],
@@ -141,6 +142,14 @@ export const baseMiddleware = async (c: Context, next: Next) => {
eventId: c.req.header("x-event-id"),
skipCacheDeletion: c.req.header("x-skip-cache-deletion") === "true",
skipWebhooks: c.req.header("x-skip-webhooks") === "true",
+ syncCoalesce:
+ process.env.NODE_ENV !== "production"
+ ? c.req.header("x-sync-coalesce") === "true"
+ ? true
+ : c.req.header("x-sync-coalesce") === "false"
+ ? false
+ : undefined
+ : undefined,
keepInternalFields: c.req.header("x-strip-internal") === "false",
useReplica: c.req.header("x-use-replica") === "true",
mockVercelApi: c.req.header("x-mock-vercel-api") === "true",
diff --git a/server/src/honoUtils/HonoEnv.ts b/server/src/honoUtils/HonoEnv.ts
index 628cccf671..176b731f4c 100644
--- a/server/src/honoUtils/HonoEnv.ts
+++ b/server/src/honoUtils/HonoEnv.ts
@@ -31,6 +31,7 @@ export type RequestContext = {
oauthResource?: string;
customerId?: string;
entityId?: string;
+ requestBody?: unknown;
// Objects
db: DrizzleCli;
@@ -93,6 +94,9 @@ export type RequestContext = {
testOptions?: {
skipCacheDeletion?: boolean;
skipWebhooks?: boolean;
+ /** Per-request sync-coalesce gate override (non-prod only); undefined
+ * falls through to the edge config. */
+ syncCoalesce?: boolean;
eventId?: string;
keepInternalFields?: boolean;
useReplica?: boolean;
diff --git a/server/src/init.ts b/server/src/init.ts
index 254807594e..1f5202a5f5 100644
--- a/server/src/init.ts
+++ b/server/src/init.ts
@@ -26,8 +26,13 @@ import "./internal/misc/customerBlocks/customerBlockStore.js";
import "./internal/misc/edgeConfig/orgLimitsStore.js";
import "./internal/misc/stripeSync/stripeSyncStore.js";
import "./internal/misc/redisV2Cache/redisV2CacheStore.js";
+import "./internal/misc/mainRedisCache/mainRedisCacheStore.js";
import "./internal/misc/cacheV2Ramp/cacheV2RampStore.js";
import "./internal/misc/jobQueues/jobQueueStore.js";
+import "./internal/misc/batchReset/batchResetConfigStore.js";
+import "./internal/misc/resetJob/resetJobStore.js";
+import "./internal/misc/resetJobV2/resetJobV2Store.js";
+
// Side-effect: configures trigger.dev SDK to use TRIGGER_SERVER_SECRET_KEY.
import "./trigger/configureTrigger.js";
import { closeStripeSyncEngine } from "@autumn/stripe-sync";
diff --git a/server/src/internal/admin/adminRouter.ts b/server/src/internal/admin/adminRouter.ts
index e46ab90fa4..663fc3e8ad 100644
--- a/server/src/internal/admin/adminRouter.ts
+++ b/server/src/internal/admin/adminRouter.ts
@@ -13,11 +13,13 @@ import {
handleUpdateAdminOrgRedisPublicUrl,
handleUpsertAdminOrgRedisConfig,
} from "./handleAdminOrgRedisConfig";
+import { handleGetAdminBatchResetConfig } from "./handleGetAdminBatchResetConfig";
import { handleGetAdminCustomerBlockConfig } from "./handleGetAdminCustomerBlockConfig";
import { handleGetAdminEdgeConfigSources } from "./handleGetAdminEdgeConfigSources";
import { handleGetAdminFeatureFlagsConfig } from "./handleGetAdminFeatureFlagsConfig";
import { handleGetAdminFullSubjectGateConfig } from "./handleGetAdminFullSubjectGateConfig";
import { handleGetAdminJobQueueConfig } from "./handleGetAdminJobQueueConfig";
+import { handleGetAdminMainRedisCacheConfig } from "./handleGetAdminMainRedisCacheConfig";
import { handleGetAdminMiscellaneousEdgeConfig } from "./handleGetAdminMiscellaneousEdgeConfig";
import { handleGetAdminOrgLimitsConfig } from "./handleGetAdminOrgLimitsConfig";
import { handleGetAdminOrgRequestBlock } from "./handleGetAdminOrgRequestBlock";
@@ -25,6 +27,8 @@ import { handleGetAdminRateLimitOverridesConfig } from "./handleGetAdminRateLimi
import { handleGetAdminRateLimitRedisAllowlistConfig } from "./handleGetAdminRateLimitRedisAllowlistConfig";
import { handleGetAdminRedisV2CacheConfig } from "./handleGetAdminRedisV2CacheConfig";
import { handleGetAdminRequestBlockConfig } from "./handleGetAdminRequestBlockConfig";
+import { handleGetAdminResetJobConfig } from "./handleGetAdminResetJobConfig";
+import { handleGetAdminResetJobV2Config } from "./handleGetAdminResetJobV2Config";
import { handleGetAdminStripeSyncConfig } from "./handleGetAdminStripeSyncConfig";
import { handleGetDefaultStripeAccount } from "./handleGetDefaultStripeAccount";
@@ -40,10 +44,12 @@ import {
handleGetSlackAdminInstall,
handleUpdateSlackAdminTarget,
} from "./handleSlackAdminChat";
+import { handleUpsertAdminBatchResetConfig } from "./handleUpsertAdminBatchResetConfig";
import { handleUpsertAdminCustomerBlockConfig } from "./handleUpsertAdminCustomerBlockConfig";
import { handleUpsertAdminFeatureFlagsConfig } from "./handleUpsertAdminFeatureFlagsConfig";
import { handleUpsertAdminFullSubjectGateConfig } from "./handleUpsertAdminFullSubjectGateConfig";
import { handleUpsertAdminJobQueueConfig } from "./handleUpsertAdminJobQueueConfig";
+import { handleUpsertAdminMainRedisCacheConfig } from "./handleUpsertAdminMainRedisCacheConfig";
import { handleUpsertAdminMiscellaneousEdgeConfig } from "./handleUpsertAdminMiscellaneousEdgeConfig";
import { handleUpsertAdminOrgLimitsConfig } from "./handleUpsertAdminOrgLimitsConfig";
import { handleUpsertAdminOrgRequestBlock } from "./handleUpsertAdminOrgRequestBlock";
@@ -51,6 +57,8 @@ import { handleUpsertAdminRateLimitOverridesConfig } from "./handleUpsertAdminRa
import { handleUpsertAdminRateLimitRedisAllowlistConfig } from "./handleUpsertAdminRateLimitRedisAllowlistConfig";
import { handleUpsertAdminRedisV2CacheConfig } from "./handleUpsertAdminRedisV2CacheConfig";
import { handleUpsertAdminRequestBlockConfig } from "./handleUpsertAdminRequestBlockConfig";
+import { handleUpsertAdminResetJobConfig } from "./handleUpsertAdminResetJobConfig";
+import { handleUpsertAdminResetJobV2Config } from "./handleUpsertAdminResetJobV2Config";
import { handleUpsertAdminStripeSyncConfig } from "./handleUpsertAdminStripeSyncConfig";
import { handleUpsertSlackMcpOAuthClient } from "./handleUpsertSlackMcpOAuthClient";
import { handleDeleteRollout } from "./rollouts/handleDeleteRollout";
@@ -149,6 +157,18 @@ honoAdminRouter.put(
);
honoAdminRouter.get("/job-queue-config", ...handleGetAdminJobQueueConfig);
honoAdminRouter.put("/job-queue-config", ...handleUpsertAdminJobQueueConfig);
+honoAdminRouter.get("/batch-reset-config", ...handleGetAdminBatchResetConfig);
+honoAdminRouter.put(
+ "/batch-reset-config",
+ ...handleUpsertAdminBatchResetConfig,
+);
+honoAdminRouter.get("/reset-job-config", ...handleGetAdminResetJobConfig);
+honoAdminRouter.put("/reset-job-config", ...handleUpsertAdminResetJobConfig);
+honoAdminRouter.get("/reset-job-v2-config", ...handleGetAdminResetJobV2Config);
+honoAdminRouter.put(
+ "/reset-job-v2-config",
+ ...handleUpsertAdminResetJobV2Config,
+);
honoAdminRouter.get("/stripe-sync-config", ...handleGetAdminStripeSyncConfig);
honoAdminRouter.put(
"/stripe-sync-config",
@@ -162,6 +182,14 @@ honoAdminRouter.put(
"/redis-v2-cache-config",
...handleUpsertAdminRedisV2CacheConfig,
);
+honoAdminRouter.get(
+ "/main-redis-cache-config",
+ ...handleGetAdminMainRedisCacheConfig,
+);
+honoAdminRouter.put(
+ "/main-redis-cache-config",
+ ...handleUpsertAdminMainRedisCacheConfig,
+);
honoAdminRouter.get("/cache-v2-ramp", ...handleGetAdminCacheV2Ramp);
honoAdminRouter.patch("/cache-v2-ramp", ...handleUpsertAdminCacheV2Ramp);
honoAdminRouter.patch(
@@ -171,7 +199,10 @@ honoAdminRouter.patch(
honoAdminRouter.delete("/cache-v2-ramp", ...handleDeleteAdminCacheV2Ramp);
honoAdminRouter.get("/org-member", ...handleGetOrgMember);
honoAdminRouter.get("/master-stripe-account", ...handleGetMasterStripeAccount);
-honoAdminRouter.get("/default-stripe-account", ...handleGetDefaultStripeAccount);
+honoAdminRouter.get(
+ "/default-stripe-account",
+ ...handleGetDefaultStripeAccount,
+);
honoAdminRouter.get("/oauth-clients", ...handleListOAuthClients);
honoAdminRouter.post(
"/oauth-clients/slack-mcp",
diff --git a/server/src/internal/admin/handleGetAdminBatchResetConfig.ts b/server/src/internal/admin/handleGetAdminBatchResetConfig.ts
new file mode 100644
index 0000000000..9bf18e0d74
--- /dev/null
+++ b/server/src/internal/admin/handleGetAdminBatchResetConfig.ts
@@ -0,0 +1,22 @@
+import { Scopes } from "@autumn/shared";
+import { createRoute } from "@/honoMiddlewares/routeHandler.js";
+import {
+ getBatchResetConfigFromSource,
+ getBatchResetConfigStatus,
+} from "@/internal/misc/batchReset/batchResetConfigStore.js";
+
+export const handleGetAdminBatchResetConfig = createRoute({
+ scopes: [Scopes.Superuser],
+ handler: async (c) => {
+ const status = getBatchResetConfigStatus();
+ const config = await getBatchResetConfigFromSource();
+
+ return c.json({
+ ...config,
+ configHealthy: status.healthy,
+ configConfigured: status.configured,
+ lastSuccessAt: status.lastSuccessAt ?? null,
+ error: status.error ?? null,
+ });
+ },
+});
diff --git a/server/src/internal/admin/handleGetAdminMainRedisCacheConfig.ts b/server/src/internal/admin/handleGetAdminMainRedisCacheConfig.ts
new file mode 100644
index 0000000000..fda85ca227
--- /dev/null
+++ b/server/src/internal/admin/handleGetAdminMainRedisCacheConfig.ts
@@ -0,0 +1,25 @@
+import { Scopes } from "@autumn/shared";
+import { getFallbackRedis } from "@/external/redis/initRedis.js";
+import { createRoute } from "@/honoMiddlewares/routeHandler.js";
+import {
+ getActiveMainRedisInstance,
+ getMainRedisCacheStatus,
+} from "@/internal/misc/mainRedisCache/mainRedisCacheStore.js";
+
+export const handleGetAdminMainRedisCacheConfig = createRoute({
+ scopes: [Scopes.Superuser],
+ handler: async (c) => {
+ const status = getMainRedisCacheStatus();
+ const fallback = getFallbackRedis();
+
+ return c.json({
+ activeInstance: getActiveMainRedisInstance(),
+ fallbackConfigured: Boolean(fallback),
+ fallbackStatus: fallback?.status ?? "not_configured",
+ configHealthy: status.healthy,
+ configConfigured: status.configured,
+ lastSuccessAt: status.lastSuccessAt ?? null,
+ error: status.error ?? null,
+ });
+ },
+});
diff --git a/server/src/internal/admin/handleGetAdminResetJobConfig.ts b/server/src/internal/admin/handleGetAdminResetJobConfig.ts
new file mode 100644
index 0000000000..836d3e3ec1
--- /dev/null
+++ b/server/src/internal/admin/handleGetAdminResetJobConfig.ts
@@ -0,0 +1,22 @@
+import { Scopes } from "@autumn/shared";
+import { createRoute } from "@/honoMiddlewares/routeHandler.js";
+import {
+ getResetJobConfigFromSource,
+ getResetJobConfigStatus,
+} from "@/internal/misc/resetJob/resetJobStore.js";
+
+export const handleGetAdminResetJobConfig = createRoute({
+ scopes: [Scopes.Superuser],
+ handler: async (c) => {
+ const status = getResetJobConfigStatus();
+ const config = await getResetJobConfigFromSource();
+
+ return c.json({
+ ...config,
+ configHealthy: status.healthy,
+ configConfigured: status.configured,
+ lastSuccessAt: status.lastSuccessAt ?? null,
+ error: status.error ?? null,
+ });
+ },
+});
diff --git a/server/src/internal/admin/handleGetAdminResetJobV2Config.ts b/server/src/internal/admin/handleGetAdminResetJobV2Config.ts
new file mode 100644
index 0000000000..338c7fdcdf
--- /dev/null
+++ b/server/src/internal/admin/handleGetAdminResetJobV2Config.ts
@@ -0,0 +1,24 @@
+import { Scopes } from "@autumn/shared";
+import { createRoute } from "@/honoMiddlewares/routeHandler.js";
+import { RESET_JOB_V2_CONFIG_LIMITS } from "@/internal/misc/resetJobV2/resetJobV2Schemas.js";
+import {
+ getResetJobV2ConfigFromSource,
+ getResetJobV2ConfigStatus,
+} from "@/internal/misc/resetJobV2/resetJobV2Store.js";
+
+export const handleGetAdminResetJobV2Config = createRoute({
+ scopes: [Scopes.Superuser],
+ handler: async (c) => {
+ const status = getResetJobV2ConfigStatus();
+ const config = await getResetJobV2ConfigFromSource();
+
+ return c.json({
+ ...config,
+ limits: RESET_JOB_V2_CONFIG_LIMITS,
+ configHealthy: status.healthy,
+ configConfigured: status.configured,
+ lastSuccessAt: status.lastSuccessAt ?? null,
+ error: status.error ?? null,
+ });
+ },
+});
diff --git a/server/src/internal/admin/handleUpsertAdminBatchResetConfig.ts b/server/src/internal/admin/handleUpsertAdminBatchResetConfig.ts
new file mode 100644
index 0000000000..e6c43b1302
--- /dev/null
+++ b/server/src/internal/admin/handleUpsertAdminBatchResetConfig.ts
@@ -0,0 +1,16 @@
+import { Scopes } from "@autumn/shared";
+import { createRoute } from "@/honoMiddlewares/routeHandler.js";
+import { BatchResetConfigSchema } from "@/internal/misc/batchReset/batchResetConfigSchemas.js";
+import { updateFullBatchResetConfig } from "@/internal/misc/batchReset/batchResetConfigStore.js";
+
+export const handleUpsertAdminBatchResetConfig = createRoute({
+ scopes: [Scopes.Superuser],
+ body: BatchResetConfigSchema,
+ handler: async (c) => {
+ const body = c.req.valid("json");
+
+ await updateFullBatchResetConfig({ config: body });
+
+ return c.json({ success: true });
+ },
+});
diff --git a/server/src/internal/admin/handleUpsertAdminMainRedisCacheConfig.ts b/server/src/internal/admin/handleUpsertAdminMainRedisCacheConfig.ts
new file mode 100644
index 0000000000..d2d61d00df
--- /dev/null
+++ b/server/src/internal/admin/handleUpsertAdminMainRedisCacheConfig.ts
@@ -0,0 +1,37 @@
+import { ErrCode, RecaseError, Scopes } from "@autumn/shared";
+import { getFallbackRedis } from "@/external/redis/initRedis.js";
+import { createRoute } from "@/honoMiddlewares/routeHandler.js";
+import { MainRedisCacheConfigSchema } from "@/internal/misc/mainRedisCache/mainRedisCacheSchemas.js";
+import { updateActiveMainRedisInstance } from "@/internal/misc/mainRedisCache/mainRedisCacheStore.js";
+
+export const handleUpsertAdminMainRedisCacheConfig = createRoute({
+ scopes: [Scopes.Superuser],
+ body: MainRedisCacheConfigSchema,
+ handler: async (c) => {
+ const { activeInstance } = c.req.valid("json");
+
+ if (activeInstance === "fallback") {
+ const fallback = getFallbackRedis();
+ if (!fallback || fallback.status !== "ready") {
+ throw new RecaseError({
+ message:
+ "CACHE_BACKUP_URL is not configured or its Redis client is not ready",
+ code: ErrCode.InvalidRequest,
+ statusCode: 503,
+ });
+ }
+
+ const pong = await fallback.ping().catch(() => null);
+ if (pong !== "PONG") {
+ throw new RecaseError({
+ message: "Fallback Redis did not respond to its readiness check",
+ code: ErrCode.InvalidRequest,
+ statusCode: 503,
+ });
+ }
+ }
+
+ await updateActiveMainRedisInstance({ activeInstance });
+ return c.json({ success: true, activeInstance });
+ },
+});
diff --git a/server/src/internal/admin/handleUpsertAdminResetJobConfig.ts b/server/src/internal/admin/handleUpsertAdminResetJobConfig.ts
new file mode 100644
index 0000000000..910ef42653
--- /dev/null
+++ b/server/src/internal/admin/handleUpsertAdminResetJobConfig.ts
@@ -0,0 +1,16 @@
+import { Scopes } from "@autumn/shared";
+import { createRoute } from "@/honoMiddlewares/routeHandler.js";
+import { ResetJobConfigSchema } from "@/internal/misc/resetJob/resetJobSchemas.js";
+import { updateResetJobConfig } from "@/internal/misc/resetJob/resetJobStore.js";
+
+export const handleUpsertAdminResetJobConfig = createRoute({
+ scopes: [Scopes.Superuser],
+ body: ResetJobConfigSchema,
+ handler: async (c) => {
+ const config = c.req.valid("json");
+
+ await updateResetJobConfig({ config });
+
+ return c.json({ success: true });
+ },
+});
diff --git a/server/src/internal/admin/handleUpsertAdminResetJobV2Config.ts b/server/src/internal/admin/handleUpsertAdminResetJobV2Config.ts
new file mode 100644
index 0000000000..6100782357
--- /dev/null
+++ b/server/src/internal/admin/handleUpsertAdminResetJobV2Config.ts
@@ -0,0 +1,16 @@
+import { Scopes } from "@autumn/shared";
+import { createRoute } from "@/honoMiddlewares/routeHandler.js";
+import { ResetJobV2ConfigSchema } from "@/internal/misc/resetJobV2/resetJobV2Schemas.js";
+import { updateResetJobV2Config } from "@/internal/misc/resetJobV2/resetJobV2Store.js";
+
+export const handleUpsertAdminResetJobV2Config = createRoute({
+ scopes: [Scopes.Superuser],
+ body: ResetJobV2ConfigSchema,
+ handler: async (c) => {
+ const config = c.req.valid("json");
+
+ await updateResetJobV2Config({ config });
+
+ return c.json({ success: true });
+ },
+});
diff --git a/server/src/internal/auth/handleListAuthOrganizations.ts b/server/src/internal/auth/handleListAuthOrganizations.ts
index 9a8d5d2276..c7c65c90eb 100644
--- a/server/src/internal/auth/handleListAuthOrganizations.ts
+++ b/server/src/internal/auth/handleListAuthOrganizations.ts
@@ -20,7 +20,7 @@ export const handleListAuthOrganizations = async (c: Context) => {
}
const orgs = await db
- .select({
+ .selectDistinct({
id: organizations.id,
name: organizations.name,
slug: organizations.slug,
diff --git a/server/src/internal/balances/batchReset/batchResetCustomerEntitlementsV2.ts b/server/src/internal/balances/batchReset/batchResetCustomerEntitlementsV2.ts
new file mode 100644
index 0000000000..d6d2d40511
--- /dev/null
+++ b/server/src/internal/balances/batchReset/batchResetCustomerEntitlementsV2.ts
@@ -0,0 +1,85 @@
+import type { DrizzleCli } from "@/db/initDrizzle.js";
+import type { Logger } from "@/external/logtail/logtailUtils.js";
+import { classifyBatchResetContext } from "./compute/classifyBatchResetContext.js";
+import { computeResetMutations } from "./compute/computeResets/computeResetMutations.js";
+import { computeVerdictMutations } from "./compute/computeVerdictMutations.js";
+import { executeResetMutations } from "./execute/executeResetMutations.js";
+import { executeVerdictMutations } from "./execute/executeVerdictMutations.js";
+import { invalidateResetCaches } from "./execute/invalidateResetCaches.js";
+import { logBatchCustomerEntitlementsV2 } from "./logs/logBatchCustomerEntitlementsV2.js";
+import { setupBatchResetContext } from "./setup/setupBatchResetContext.js";
+import type { BatchResetCustomerEntitlementsV2Payload } from "./types.js";
+
+/**
+ * SQS handler: hydrates scanned customer entitlement IDs, builds one context
+ * per org+env, classifies every candidate, then computes and persists batched
+ * verdict and reset mutations.
+ */
+export const batchResetCustomerEntitlementsV2 = async ({
+ db,
+ logger,
+ payload,
+}: {
+ db: DrizzleCli;
+ logger: Logger;
+ payload: BatchResetCustomerEntitlementsV2Payload;
+}) => {
+ const batchResetContext = await setupBatchResetContext({
+ db,
+ logger,
+ payload,
+ });
+
+ const classifiedBatchResetContext = classifyBatchResetContext({
+ batchResetContext,
+ });
+
+ const verdictMutations = computeVerdictMutations({
+ verdicts: classifiedBatchResetContext.verdicts,
+ });
+
+ const resetMutations = await computeResetMutations({
+ resetGroups: classifiedBatchResetContext.resetGroups,
+ });
+
+ logBatchCustomerEntitlementsV2({
+ logger,
+ payload,
+ batchResetContext,
+ classifiedBatchResetContext,
+ verdictMutations,
+ resetMutations,
+ });
+
+ await executeVerdictMutations({ db, verdictMutations });
+ const { appliedCustomerEntitlementIds, staleSkippedCount } =
+ await executeResetMutations({ db, resetMutations });
+
+ if (staleSkippedCount > 0) {
+ // A concurrent reset (usually a lazy reset racing this worker) won —
+ // the guarded UPDATE skipped those rows instead of double-applying.
+ logger.info("[batchReset] stale mutations skipped", {
+ jobName: "reset-cus-ents-v2",
+ data: { staleSkippedCount },
+ });
+ }
+
+ const appliedResetMutations = resetMutations.filter(
+ ({ customerEntitlementId }) =>
+ appliedCustomerEntitlementIds.has(customerEntitlementId),
+ );
+
+ // Postgres is authoritative now — drop the stale Redis state so the next
+ // read rehydrates instead of serving pre-reset balances.
+ await invalidateResetCaches({
+ resetGroups: classifiedBatchResetContext.resetGroups,
+ resetMutations: appliedResetMutations,
+ });
+
+ return {
+ ...classifiedBatchResetContext,
+ verdictMutations,
+ resetMutations,
+ appliedResetMutations,
+ };
+};
diff --git a/server/src/internal/balances/batchReset/compute/classifyBatchResetContext.ts b/server/src/internal/balances/batchReset/compute/classifyBatchResetContext.ts
new file mode 100644
index 0000000000..32610c4b96
--- /dev/null
+++ b/server/src/internal/balances/batchReset/compute/classifyBatchResetContext.ts
@@ -0,0 +1,52 @@
+import type { ResetContextCustomerEntitlement } from "@/internal/customers/cusProducts/cusEnts/repos/getResetContextByIds.js";
+import type {
+ BatchResetContext,
+ BatchResetGroup,
+ ResetVerdict,
+} from "../types.js";
+import { classifyResetCandidate } from "./classifyResetCandidate.js";
+
+export type ClassifiedBatchResetContext = {
+ /** Groups containing only customer entitlements that should be reset. */
+ resetGroups: BatchResetGroup[];
+ /** All non-reset outcomes, flattened for one later bulk write. */
+ verdicts: ResetVerdict[];
+};
+
+/**
+ * Pure classification stage for one hydrated SQS batch.
+ *
+ * Org contexts stay attached to resettable entitlements because reset
+ * computation depends on org configuration. Verdicts are tenant-independent
+ * row updates, so they are flattened for the later persistence stage.
+ */
+export const classifyBatchResetContext = ({
+ batchResetContext,
+}: {
+ batchResetContext: BatchResetContext;
+}): ClassifiedBatchResetContext => {
+ const resetGroups: BatchResetGroup[] = [];
+ const verdicts: ResetVerdict[] = [];
+
+ for (const group of batchResetContext.groups) {
+ const resettable: ResetContextCustomerEntitlement[] = [];
+
+ for (const customerEntitlement of group.customerEntitlements) {
+ const verdict = classifyResetCandidate({ customerEntitlement });
+ if (verdict) {
+ verdicts.push(verdict);
+ } else {
+ resettable.push(customerEntitlement);
+ }
+ }
+
+ if (resettable.length > 0) {
+ resetGroups.push({
+ ctx: group.ctx,
+ customerEntitlements: resettable,
+ });
+ }
+ }
+
+ return { resetGroups, verdicts };
+};
diff --git a/server/src/internal/balances/batchReset/compute/classifyNoAction.ts b/server/src/internal/balances/batchReset/compute/classifyNoAction.ts
new file mode 100644
index 0000000000..05b6257b2c
--- /dev/null
+++ b/server/src/internal/balances/batchReset/compute/classifyNoAction.ts
@@ -0,0 +1,93 @@
+import {
+ CusProductStatus,
+ isPooledBalanceSourceCustomerEntitlement,
+ isSyntheticPooledBalanceCustomerEntitlement,
+ PooledBalanceResetMode,
+} from "@autumn/shared";
+import type { ResetContextCustomerEntitlement } from "@/internal/customers/cusProducts/cusEnts/repos/getResetContextByIds.js";
+import type { ResetVerdict } from "../types.js";
+
+/** Classifies candidates that should not be mutated by this worker. */
+export const classifyNoAction = ({
+ customerEntitlement,
+}: {
+ customerEntitlement: ResetContextCustomerEntitlement;
+}): ResetVerdict | null => {
+ // Not overdue (or no reset schedule at all): the scanner re-enqueues
+ // across sweeps and SQS is at-least-once, so redelivered rows that were
+ // already reset MUST be a no-op — otherwise a redelivery would re-top the
+ // balance and push next_reset_at another cycle forward.
+ if (
+ customerEntitlement.next_reset_at == null ||
+ customerEntitlement.next_reset_at >= Date.now()
+ ) {
+ return {
+ kind: "no_action",
+ customerEntitlementId: customerEntitlement.id,
+ reason: "not_due",
+ };
+ }
+
+ if (
+ isPooledBalanceSourceCustomerEntitlement({ customerEntitlement })
+ ) {
+ return {
+ kind: "no_action",
+ customerEntitlementId: customerEntitlement.id,
+ reason: "pooled_balance_source",
+ };
+ }
+
+ if (
+ isSyntheticPooledBalanceCustomerEntitlement({ customerEntitlement })
+ ) {
+ if (!customerEntitlement.pooled_balance) {
+ return {
+ kind: "no_action",
+ customerEntitlementId: customerEntitlement.id,
+ reason: "pooled_balance_missing",
+ };
+ }
+
+ if (
+ customerEntitlement.pooled_balance.reset_mode ===
+ PooledBalanceResetMode.Subscription
+ ) {
+ return {
+ kind: "resets_via_invoice",
+ customerEntitlementId: customerEntitlement.id,
+ };
+ }
+ }
+
+ const customerProduct = customerEntitlement.customer_product;
+ if (!customerProduct) return null;
+
+ if (
+ customerProduct.status === CusProductStatus.PastDue &&
+ !customerProduct.product.config?.ignore_past_due
+ ) {
+ return {
+ kind: "no_action",
+ customerEntitlementId: customerEntitlement.id,
+ reason: "product_past_due",
+ };
+ }
+
+ // Parity with the lazy path (and the V1 cron): only Active and gated
+ // PastDue products reset. Scheduled/Trialing/Paused/etc. must not have
+ // their balances topped up or next_reset_at advanced. (Expired products
+ // are handled earlier by classifyShouldExpire.)
+ if (
+ customerProduct.status !== CusProductStatus.Active &&
+ customerProduct.status !== CusProductStatus.PastDue
+ ) {
+ return {
+ kind: "no_action",
+ customerEntitlementId: customerEntitlement.id,
+ reason: "product_not_active",
+ };
+ }
+
+ return null;
+};
diff --git a/server/src/internal/balances/batchReset/compute/classifyResetCandidate.ts b/server/src/internal/balances/batchReset/compute/classifyResetCandidate.ts
new file mode 100644
index 0000000000..c6a4f570a7
--- /dev/null
+++ b/server/src/internal/balances/batchReset/compute/classifyResetCandidate.ts
@@ -0,0 +1,58 @@
+import {
+ cusEntToCusPrice,
+ isCustomerEntitlementPrepaidWithSeparateResetInterval,
+ isLifetimeEntitlement,
+ isUnlimitedEntitlement,
+} from "@autumn/shared";
+import type { ResetContextCustomerEntitlement } from "@/internal/customers/cusProducts/cusEnts/repos/getResetContextByIds.js";
+import type { ResetVerdict } from "../types.js";
+import { classifyNoAction } from "./classifyNoAction.js";
+import { classifyShouldExpire } from "./classifyShouldExpire.js";
+
+export const classifyResetCandidate = ({
+ customerEntitlement,
+}: {
+ customerEntitlement: ResetContextCustomerEntitlement;
+}): ResetVerdict | null => {
+ const customerEntitlementId = customerEntitlement.id;
+ const entitlement = customerEntitlement.entitlement;
+
+ const shouldExpire = classifyShouldExpire({ customerEntitlement });
+ if (shouldExpire) return shouldExpire;
+
+ const noAction = classifyNoAction({ customerEntitlement });
+ if (noAction) return noAction;
+
+ if (isUnlimitedEntitlement({ entitlement })) {
+ return { kind: "clear_next_reset", customerEntitlementId, unlimited: true };
+ }
+
+ if (isLifetimeEntitlement({ entitlement })) {
+ return {
+ kind: "clear_next_reset",
+ customerEntitlementId,
+ unlimited: false,
+ };
+ }
+
+ const customerProduct = customerEntitlement.customer_product;
+ if (!customerProduct) return null;
+
+ // Price-backed entitlements on a live subscription reset from
+ // invoice.created, not here — except split prepaid reset intervals.
+ // Without a subscription there is no invoice, so the reset stays ours.
+ const customerPrice = cusEntToCusPrice({ cusEnt: customerEntitlement });
+ const hasSubscription = (customerProduct.subscription_ids?.length ?? 0) > 0;
+ if (
+ customerPrice &&
+ hasSubscription &&
+ !isCustomerEntitlementPrepaidWithSeparateResetInterval({
+ customerEntitlement,
+ customerPrice,
+ })
+ ) {
+ return { kind: "resets_via_invoice", customerEntitlementId };
+ }
+
+ return null;
+};
diff --git a/server/src/internal/balances/batchReset/compute/classifyShouldExpire.ts b/server/src/internal/balances/batchReset/compute/classifyShouldExpire.ts
new file mode 100644
index 0000000000..8b605cede0
--- /dev/null
+++ b/server/src/internal/balances/batchReset/compute/classifyShouldExpire.ts
@@ -0,0 +1,23 @@
+import { CusProductStatus } from "@autumn/shared";
+import type { ResetContextCustomerEntitlement } from "@/internal/customers/cusProducts/cusEnts/repos/getResetContextByIds.js";
+import type { ResetVerdict } from "../types.js";
+
+/**
+ * Marks a customer entitlement for expiry denormalization when its owning
+ * customer product is expired but the denormalized flag is not yet true.
+ */
+export const classifyShouldExpire = ({
+ customerEntitlement,
+}: {
+ customerEntitlement: ResetContextCustomerEntitlement;
+}): ResetVerdict | null => {
+ if (customerEntitlement.expired === true) return null;
+
+ const customerProduct = customerEntitlement.customer_product;
+ if (customerProduct?.status !== CusProductStatus.Expired) return null;
+
+ return {
+ kind: "should_expire",
+ customerEntitlementId: customerEntitlement.id,
+ };
+};
diff --git a/server/src/internal/balances/batchReset/compute/computeResets/computeResetMutation.ts b/server/src/internal/balances/batchReset/compute/computeResets/computeResetMutation.ts
new file mode 100644
index 0000000000..85b864aefa
--- /dev/null
+++ b/server/src/internal/balances/batchReset/compute/computeResets/computeResetMutation.ts
@@ -0,0 +1,59 @@
+import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
+import { processReset } from "@/internal/customers/actions/resetCustomerEntitlements/processReset.js";
+import { performMaximumClearing } from "@/internal/customers/cusProducts/cusEnts/cusRollovers/rolloverUtils.js";
+import type { ResetContextCustomerEntitlement } from "@/internal/customers/cusProducts/cusEnts/repos/getResetContextByIds.js";
+import type { ResetMutation } from "../../types.js";
+
+export const computeResetMutation = async ({
+ ctx,
+ customerEntitlement,
+}: {
+ ctx: AutumnContext;
+ customerEntitlement: ResetContextCustomerEntitlement;
+}): Promise => {
+ // Guaranteed non-null by the not_due classification; re-checked for the
+ // optimistic guard the execute UPDATE compares against.
+ const expectedNextResetAt = customerEntitlement.next_reset_at;
+ if (expectedNextResetAt == null) return null;
+
+ const result = await processReset({
+ ctx,
+ cusEnt: customerEntitlement,
+ });
+
+ if (!result) return null;
+
+ const newRollovers = result.rolloverInsert?.rows ?? [];
+ if (newRollovers.length === 0) {
+ return {
+ customerEntitlementId: customerEntitlement.id,
+ expectedNextResetAt,
+ updates: result.updates,
+ rolloverInserts: [],
+ rolloverUpdates: [],
+ rolloverDeleteIds: [],
+ };
+ }
+
+ const { toDelete, toUpdate } = performMaximumClearing({
+ rows: [...customerEntitlement.rollovers, ...newRollovers],
+ cusEnt: customerEntitlement,
+ });
+
+ const newRolloverIds = new Set(newRollovers.map(({ id }) => id));
+ const deletedRolloverIds = new Set(toDelete);
+ const updatedRolloversById = new Map(
+ toUpdate.map((rollover) => [rollover.id, rollover]),
+ );
+
+ return {
+ customerEntitlementId: customerEntitlement.id,
+ expectedNextResetAt,
+ updates: result.updates,
+ rolloverInserts: newRollovers
+ .filter(({ id }) => !deletedRolloverIds.has(id))
+ .map((rollover) => updatedRolloversById.get(rollover.id) ?? rollover),
+ rolloverUpdates: toUpdate.filter(({ id }) => !newRolloverIds.has(id)),
+ rolloverDeleteIds: toDelete.filter((id) => !newRolloverIds.has(id)),
+ };
+};
diff --git a/server/src/internal/balances/batchReset/compute/computeResets/computeResetMutations.ts b/server/src/internal/balances/batchReset/compute/computeResets/computeResetMutations.ts
new file mode 100644
index 0000000000..2b08821c63
--- /dev/null
+++ b/server/src/internal/balances/batchReset/compute/computeResets/computeResetMutations.ts
@@ -0,0 +1,29 @@
+import pLimit from "p-limit";
+import type { BatchResetGroup, ResetMutation } from "../../types.js";
+import { computeResetMutation } from "./computeResetMutation.js";
+
+const RESET_MUTATION_CONCURRENCY = 25;
+
+export const computeResetMutations = async ({
+ resetGroups,
+}: {
+ resetGroups: BatchResetGroup[];
+}): Promise => {
+ const limit = pLimit(RESET_MUTATION_CONCURRENCY);
+ const mutations = await Promise.all(
+ resetGroups.flatMap((group) =>
+ group.customerEntitlements.map((customerEntitlement) =>
+ limit(() =>
+ computeResetMutation({
+ ctx: group.ctx,
+ customerEntitlement,
+ }),
+ ),
+ ),
+ ),
+ );
+
+ return mutations.filter(
+ (mutation): mutation is ResetMutation => mutation !== null,
+ );
+};
diff --git a/server/src/internal/balances/batchReset/compute/computeVerdictMutations.ts b/server/src/internal/balances/batchReset/compute/computeVerdictMutations.ts
new file mode 100644
index 0000000000..25382657d3
--- /dev/null
+++ b/server/src/internal/balances/batchReset/compute/computeVerdictMutations.ts
@@ -0,0 +1,16 @@
+import type { ResetVerdict, VerdictMutations } from "../types.js";
+
+export const computeVerdictMutations = ({
+ verdicts,
+}: {
+ verdicts: ResetVerdict[];
+}): VerdictMutations => ({
+ expireCustomerEntitlementIds: verdicts.flatMap((verdict) =>
+ verdict.kind === "should_expire" ? [verdict.customerEntitlementId] : [],
+ ),
+ resetByInvoiceCustomerEntitlementIds: verdicts.flatMap((verdict) =>
+ verdict.kind === "resets_via_invoice"
+ ? [verdict.customerEntitlementId]
+ : [],
+ ),
+});
diff --git a/server/src/internal/balances/batchReset/concurrency/batchResetScanGates.ts b/server/src/internal/balances/batchReset/concurrency/batchResetScanGates.ts
new file mode 100644
index 0000000000..1b7b91dd3f
--- /dev/null
+++ b/server/src/internal/balances/batchReset/concurrency/batchResetScanGates.ts
@@ -0,0 +1,113 @@
+import { ms } from "@autumn/shared";
+import type { Logger } from "@/external/logtail/logtailUtils.js";
+import { getResetJobV2Config } from "@/internal/misc/resetJobV2/resetJobV2Store.js";
+import { getBatchResetQueueDepth } from "./getBatchResetQueueDepth.js";
+import { sleepWithAbort } from "./sleepWithAbort.js";
+
+// SQS counts are approximate — only trust "empty" after consecutive reads.
+const CONSECUTIVE_EMPTY_READS_REQUIRED = 2;
+const BARRIER_STUCK_WARN_MS = ms.minutes(30);
+
+/**
+ * Gate A — in-sweep backpressure. Blocks while the batch reset queue holds
+ * more than queueHighWaterMessages, so the scanner never runs further ahead
+ * of the workers than one high-water mark of messages.
+ *
+ * Fails open (with a warning) on SQS errors or when no dedicated queue is
+ * configured — a metrics hiccup should not stop resets.
+ */
+export const waitForQueueBelowHighWater = async ({
+ logger,
+ signal,
+}: {
+ logger: Logger;
+ signal: AbortSignal;
+}): Promise => {
+ while (!signal.aborted) {
+ let depth: Awaited>;
+ try {
+ depth = await getBatchResetQueueDepth();
+ } catch (error) {
+ logger.warn(
+ `[reset-cus-ents-v2] queue depth check failed, scanning without backpressure: ${error}`,
+ );
+ return;
+ }
+
+ const { queueHighWaterMessages, queueDepthPollMs } = getResetJobV2Config();
+ if (!depth || depth.total <= queueHighWaterMessages) return;
+
+ logger.info("[reset-cus-ents-v2] queue above high water, pausing scan", {
+ jobName: "reset-cus-ents-v2",
+ data: {
+ queueVisible: depth.visible,
+ queueInFlight: depth.inFlight,
+ highWater: queueHighWaterMessages,
+ },
+ });
+ await sleepWithAbort({ delayMs: queueDepthPollMs, signal });
+ }
+};
+
+/**
+ * Gate B — sweep-restart barrier. Blocks until the batch reset queue is
+ * fully drained (zero visible AND zero in-flight, on consecutive reads), so
+ * a new sweep can never re-enqueue rows the workers are still processing.
+ *
+ * Never force-restarts: if workers are wedged, restarting would only
+ * manufacture duplicate work — after BARRIER_STUCK_WARN_MS it logs loudly
+ * and keeps waiting. Fails open on SQS errors or when no dedicated queue is
+ * configured.
+ */
+export const waitForQueueDrained = async ({
+ logger,
+ signal,
+}: {
+ logger: Logger;
+ signal: AbortSignal;
+}): Promise => {
+ const startedAt = Date.now();
+ let consecutiveEmptyReads = 0;
+ let warnedStuck = false;
+
+ while (!signal.aborted) {
+ let depth: Awaited>;
+ try {
+ depth = await getBatchResetQueueDepth();
+ } catch (error) {
+ logger.warn(
+ `[reset-cus-ents-v2] queue depth check failed, restarting sweep without barrier: ${error}`,
+ );
+ return;
+ }
+
+ if (!depth) return;
+
+ if (depth.total === 0) {
+ consecutiveEmptyReads++;
+ if (consecutiveEmptyReads >= CONSECUTIVE_EMPTY_READS_REQUIRED) return;
+ } else {
+ consecutiveEmptyReads = 0;
+ }
+
+ if (!warnedStuck && Date.now() - startedAt > BARRIER_STUCK_WARN_MS) {
+ warnedStuck = true;
+ logger.error(
+ "[reset-cus-ents-v2] sweep barrier stuck — workers not draining the batch reset queue",
+ {
+ jobName: "reset-cus-ents-v2",
+ data: {
+ queueVisible: depth.visible,
+ queueInFlight: depth.inFlight,
+ waitedMs: Date.now() - startedAt,
+ },
+ },
+ );
+ }
+
+ await sleepWithAbort({
+ delayMs: getResetJobV2Config().queueDepthPollMs,
+ signal,
+ });
+ }
+};
diff --git a/server/src/internal/balances/batchReset/concurrency/getBatchResetQueueDepth.ts b/server/src/internal/balances/batchReset/concurrency/getBatchResetQueueDepth.ts
new file mode 100644
index 0000000000..7549d85a8c
--- /dev/null
+++ b/server/src/internal/balances/batchReset/concurrency/getBatchResetQueueDepth.ts
@@ -0,0 +1,40 @@
+import { GetQueueAttributesCommand } from "@aws-sdk/client-sqs";
+import { getSqsClient } from "@/queue/initSqs.js";
+
+export type BatchResetQueueDepth = {
+ visible: number;
+ inFlight: number;
+ total: number;
+};
+
+/**
+ * Depth of the dedicated batch reset queue: visible (waiting) plus in-flight
+ * (being processed) messages. Returns null when no dedicated queue is
+ * configured — the primary-queue fallback carries unrelated jobs, so its
+ * counts are meaningless for scan gating.
+ */
+export const getBatchResetQueueDepth =
+ async (): Promise => {
+ const queueUrl = process.env.BATCH_RESET_SQS_QUEUE_URL;
+ if (!queueUrl) return null;
+
+ const sqsClient = getSqsClient({ queueUrl });
+ const response = await sqsClient.send(
+ new GetQueueAttributesCommand({
+ QueueUrl: queueUrl,
+ AttributeNames: [
+ "ApproximateNumberOfMessages",
+ "ApproximateNumberOfMessagesNotVisible",
+ ],
+ }),
+ );
+
+ const visible = Number(
+ response.Attributes?.ApproximateNumberOfMessages ?? 0,
+ );
+ const inFlight = Number(
+ response.Attributes?.ApproximateNumberOfMessagesNotVisible ?? 0,
+ );
+
+ return { visible, inFlight, total: visible + inFlight };
+ };
diff --git a/server/src/internal/balances/batchReset/concurrency/sleepWithAbort.ts b/server/src/internal/balances/batchReset/concurrency/sleepWithAbort.ts
new file mode 100644
index 0000000000..56a901e40a
--- /dev/null
+++ b/server/src/internal/balances/batchReset/concurrency/sleepWithAbort.ts
@@ -0,0 +1,19 @@
+/** Sleeps for delayMs, resolving early if the signal aborts. */
+export const sleepWithAbort = async ({
+ delayMs,
+ signal,
+}: {
+ delayMs: number;
+ signal: AbortSignal;
+}) => {
+ if (signal.aborted) return;
+ await new Promise((resolve) => {
+ const finish = () => {
+ clearTimeout(timer);
+ signal.removeEventListener("abort", finish);
+ resolve();
+ };
+ const timer = setTimeout(finish, delayMs);
+ signal.addEventListener("abort", finish, { once: true });
+ });
+};
diff --git a/server/src/internal/balances/batchReset/enqueueBatchResetTask.ts b/server/src/internal/balances/batchReset/enqueueBatchResetTask.ts
new file mode 100644
index 0000000000..a4dbc1a646
--- /dev/null
+++ b/server/src/internal/balances/batchReset/enqueueBatchResetTask.ts
@@ -0,0 +1,30 @@
+import type { Logger } from "@/external/logtail/logtailUtils.js";
+import { JobName } from "@/queue/JobName.js";
+import { addTaskToQueue } from "@/queue/queueUtils.js";
+import type { BatchResetCustomerEntitlementsV2Payload } from "./types.js";
+
+/**
+ * Enqueues a batch reset onto its dedicated SQS queue
+ * (BATCH_RESET_SQS_QUEUE_URL). Falls back to the primary queue when unset so
+ * dev environments without the queue still process resets.
+ */
+export const enqueueBatchResetTask = async ({
+ payload,
+ logger,
+}: {
+ payload: BatchResetCustomerEntitlementsV2Payload;
+ logger: Logger;
+}) => {
+ const queueUrl = process.env.BATCH_RESET_SQS_QUEUE_URL;
+ if (!queueUrl) {
+ logger.warn(
+ "[batchReset] BATCH_RESET_SQS_QUEUE_URL unset, falling back to primary queue",
+ );
+ }
+
+ await addTaskToQueue({
+ jobName: JobName.BatchResetCustomerEntitlementsV2,
+ payload,
+ queueUrl,
+ });
+};
diff --git a/server/src/internal/balances/batchReset/execute/executeResetMutations.ts b/server/src/internal/balances/batchReset/execute/executeResetMutations.ts
new file mode 100644
index 0000000000..eef9eda8c5
--- /dev/null
+++ b/server/src/internal/balances/batchReset/execute/executeResetMutations.ts
@@ -0,0 +1,117 @@
+import { customerEntitlements } from "@autumn/shared";
+import { sql } from "drizzle-orm";
+import type { DrizzleCli } from "@/db/initDrizzle.js";
+import { withStatementTimeout } from "@/db/withStatementTimeout.js";
+import { RolloverService } from "@/internal/customers/cusProducts/cusEnts/cusRollovers/RolloverService.js";
+import { resetCronQueryTag } from "../resetCronQueryTag.js";
+import type { ResetMutation } from "../types.js";
+
+/**
+ * Applies the balance updates with an optimistic guard: a row only updates
+ * while its next_reset_at still matches what the mutation was computed from.
+ * A row that changed in between (a lazy reset racing the worker, or a
+ * duplicate delivery) is skipped. Returns the IDs that actually applied.
+ */
+const updateCustomerEntitlements = async ({
+ db,
+ resetMutations,
+}: {
+ db: DrizzleCli;
+ resetMutations: ResetMutation[];
+}): Promise> => {
+ const updates = resetMutations.map(
+ ({ customerEntitlementId, expectedNextResetAt, updates }) => ({
+ id: customerEntitlementId,
+ expected_next_reset_at: expectedNextResetAt,
+ ...updates,
+ }),
+ );
+
+ const appliedRows = await db.execute<{ id: string }>(sql`
+ UPDATE ${customerEntitlements} AS customer_entitlement
+ SET
+ balance = COALESCE(reset_update.balance, customer_entitlement.balance),
+ additional_balance = COALESCE(
+ reset_update.additional_balance,
+ customer_entitlement.additional_balance
+ ),
+ adjustment = reset_update.adjustment,
+ entities = COALESCE(reset_update.entities, customer_entitlement.entities),
+ next_reset_at = reset_update.next_reset_at,
+ cache_version = COALESCE(customer_entitlement.cache_version, 0) + 1
+ FROM jsonb_to_recordset(${JSON.stringify(updates)}::jsonb) AS reset_update(
+ id text,
+ expected_next_reset_at numeric,
+ balance numeric,
+ additional_balance numeric,
+ adjustment numeric,
+ entities jsonb,
+ next_reset_at numeric
+ )
+ WHERE customer_entitlement.id = reset_update.id
+ AND customer_entitlement.next_reset_at = reset_update.expected_next_reset_at
+ RETURNING customer_entitlement.id
+ ${resetCronQueryTag("updateBalances")}
+ `);
+
+ return new Set(appliedRows.map((row) => row.id));
+};
+
+export const executeResetMutations = async ({
+ db,
+ resetMutations,
+}: {
+ db: DrizzleCli;
+ resetMutations: ResetMutation[];
+}): Promise<{
+ appliedCustomerEntitlementIds: Set;
+ staleSkippedCount: number;
+}> => {
+ if (resetMutations.length === 0) {
+ return { appliedCustomerEntitlementIds: new Set(), staleSkippedCount: 0 };
+ }
+
+ let appliedCustomerEntitlementIds = new Set();
+
+ await withStatementTimeout(db, async (transaction) => {
+ appliedCustomerEntitlementIds = await updateCustomerEntitlements({
+ db: transaction,
+ resetMutations,
+ });
+
+ // Rollover writes only for rows whose guarded UPDATE applied — a stale
+ // mutation's rollover would double-credit a row someone else already
+ // reset.
+ const appliedMutations = resetMutations.filter(
+ ({ customerEntitlementId }) =>
+ appliedCustomerEntitlementIds.has(customerEntitlementId),
+ );
+
+ const rolloverWrites = appliedMutations.flatMap(
+ ({ rolloverInserts, rolloverUpdates }) => [
+ ...rolloverInserts,
+ ...rolloverUpdates,
+ ],
+ );
+ const rolloverDeleteIds = appliedMutations.flatMap(
+ ({ rolloverDeleteIds }) => rolloverDeleteIds,
+ );
+
+ await RolloverService.upsert({
+ db: transaction,
+ rows: rolloverWrites,
+ queryTag: resetCronQueryTag("upsertRollovers"),
+ });
+ await RolloverService.delete({
+ db: transaction,
+ ids: rolloverDeleteIds,
+ queryTag: resetCronQueryTag("deleteRollovers"),
+ });
+ });
+
+ return {
+ appliedCustomerEntitlementIds,
+ staleSkippedCount:
+ resetMutations.length - appliedCustomerEntitlementIds.size,
+ };
+};
diff --git a/server/src/internal/balances/batchReset/execute/executeVerdictMutations.ts b/server/src/internal/balances/batchReset/execute/executeVerdictMutations.ts
new file mode 100644
index 0000000000..17b0b04d59
--- /dev/null
+++ b/server/src/internal/balances/batchReset/execute/executeVerdictMutations.ts
@@ -0,0 +1,36 @@
+import { customerEntitlements } from "@autumn/shared";
+import { sql } from "drizzle-orm";
+import type { DrizzleCli } from "@/db/initDrizzle.js";
+import { resetCronQueryTag } from "../resetCronQueryTag.js";
+import type { VerdictMutations } from "../types.js";
+
+export const executeVerdictMutations = async ({
+ db,
+ verdictMutations,
+}: {
+ db: DrizzleCli;
+ verdictMutations: VerdictMutations;
+}) => {
+ const { expireCustomerEntitlementIds, resetByInvoiceCustomerEntitlementIds } =
+ verdictMutations;
+
+ if (expireCustomerEntitlementIds.length > 0) {
+ await db.execute(sql`
+ UPDATE ${customerEntitlements}
+ SET expired = true
+ WHERE id = ANY(${sql.param(expireCustomerEntitlementIds)}::text[])
+ AND expired IS NOT TRUE
+ ${resetCronQueryTag("expireEntitlements")}
+ `);
+ }
+
+ if (resetByInvoiceCustomerEntitlementIds.length > 0) {
+ await db.execute(sql`
+ UPDATE ${customerEntitlements}
+ SET reset_by_invoice = true
+ WHERE id = ANY(${sql.param(resetByInvoiceCustomerEntitlementIds)}::text[])
+ AND reset_by_invoice IS NOT TRUE
+ ${resetCronQueryTag("markInvoiceReset")}
+ `);
+ }
+};
diff --git a/server/src/internal/balances/batchReset/execute/invalidateResetCaches.ts b/server/src/internal/balances/batchReset/execute/invalidateResetCaches.ts
new file mode 100644
index 0000000000..87a91bcc0d
--- /dev/null
+++ b/server/src/internal/balances/batchReset/execute/invalidateResetCaches.ts
@@ -0,0 +1,62 @@
+import type { Redis } from "ioredis";
+import { resolveCustomerRedisRouting } from "@/external/redis/customerRedisRouting.js";
+import type { CustomerEntitlementBalanceInvalidation } from "@/internal/customers/cache/fullSubject/actions/invalidate/batchInvalidateCustomerEntitlementBalances.js";
+import { batchInvalidateCustomerEntitlementBalances } from "@/internal/customers/cache/fullSubject/actions/invalidate/batchInvalidateCustomerEntitlementBalances.js";
+import type { BatchResetGroup, ResetMutation } from "../types.js";
+
+/**
+ * Drops the V2 shared subject-balance hash state for every customer
+ * entitlement a reset mutation touched: one HDEL pipeline (cusEnt field +
+ * aggregated field) per routed redis client. Pipelines never span groups, so
+ * each stays org-scoped (cluster slot safety).
+ *
+ * Deletion, not patching — the next read rehydrates from Postgres. The
+ * legacy FullCustomer cache is dead and intentionally not touched.
+ */
+export const invalidateResetCaches = async ({
+ resetGroups,
+ resetMutations,
+}: {
+ resetGroups: BatchResetGroup[];
+ resetMutations: ResetMutation[];
+}) => {
+ const mutatedIds = new Set(
+ resetMutations.map(({ customerEntitlementId }) => customerEntitlementId),
+ );
+ if (mutatedIds.size === 0) return;
+
+ for (const group of resetGroups) {
+ const invalidationsByRedis = new Map<
+ Redis,
+ CustomerEntitlementBalanceInvalidation[]
+ >();
+
+ for (const customerEntitlement of group.customerEntitlements) {
+ if (!mutatedIds.has(customerEntitlement.id)) continue;
+
+ const customer = customerEntitlement.customer;
+ if (!customer.id) continue;
+
+ const routing = resolveCustomerRedisRouting({
+ org: group.ctx.org,
+ customerId: customer.id,
+ });
+ const invalidations = invalidationsByRedis.get(routing.redis) ?? [];
+ invalidations.push({
+ orgId: customer.org_id,
+ env: customer.env,
+ customerId: customer.id,
+ featureId: customerEntitlement.entitlement.feature.id,
+ customerEntitlementId: customerEntitlement.id,
+ });
+ invalidationsByRedis.set(routing.redis, invalidations);
+ }
+
+ for (const [redisV2, invalidations] of invalidationsByRedis) {
+ await batchInvalidateCustomerEntitlementBalances({
+ redisV2,
+ invalidations,
+ });
+ }
+ }
+};
diff --git a/server/src/internal/balances/batchReset/logs/logBatchCustomerEntitlementsV2.ts b/server/src/internal/balances/batchReset/logs/logBatchCustomerEntitlementsV2.ts
new file mode 100644
index 0000000000..37210c2dd9
--- /dev/null
+++ b/server/src/internal/balances/batchReset/logs/logBatchCustomerEntitlementsV2.ts
@@ -0,0 +1,43 @@
+import type { Logger } from "@/external/logtail/logtailUtils.js";
+import type { ClassifiedBatchResetContext } from "../compute/classifyBatchResetContext.js";
+import type {
+ BatchResetContext,
+ BatchResetCustomerEntitlementsV2Payload,
+ ResetMutation,
+ VerdictMutations,
+} from "../types.js";
+
+export const logBatchCustomerEntitlementsV2 = ({
+ logger,
+ payload,
+ batchResetContext,
+ classifiedBatchResetContext,
+ verdictMutations,
+ resetMutations,
+}: {
+ logger: Logger;
+ payload: BatchResetCustomerEntitlementsV2Payload;
+ batchResetContext: BatchResetContext;
+ classifiedBatchResetContext: ClassifiedBatchResetContext;
+ verdictMutations: VerdictMutations;
+ resetMutations: ResetMutation[];
+}) => {
+ const resettableCount = classifiedBatchResetContext.resetGroups.reduce(
+ (total, group) => total + group.customerEntitlements.length,
+ 0,
+ );
+
+ logger.info("[batchReset] mutations computed", {
+ data: {
+ requested: payload.customerEntitlementIds.length,
+ missing: batchResetContext.missingIds.length,
+ orgs: batchResetContext.groups.length,
+ resetOrgs: classifiedBatchResetContext.resetGroups.length,
+ resettable: resettableCount,
+ customerEntitlementsToExpire:
+ verdictMutations.expireCustomerEntitlementIds.length,
+ resetMutations: resetMutations.length,
+ verdicts: classifiedBatchResetContext.verdicts.length,
+ },
+ });
+};
diff --git a/server/src/internal/balances/batchReset/logs/logResetBacklogGauge.ts b/server/src/internal/balances/batchReset/logs/logResetBacklogGauge.ts
new file mode 100644
index 0000000000..09a7492235
--- /dev/null
+++ b/server/src/internal/balances/batchReset/logs/logResetBacklogGauge.ts
@@ -0,0 +1,45 @@
+import type { CronContext } from "@/cron/utils/CronContext.js";
+import type { ResetScanCursor } from "@/internal/customers/cusProducts/cusEnts/repos/getResetEligibleCustomerEntitlementsPage.js";
+import { customerEntitlementsRepo } from "@/internal/customers/cusProducts/cusEnts/repos/index.js";
+import { getBatchResetQueueDepth } from "../concurrency/getBatchResetQueueDepth.js";
+
+// Cap keeps the count cheap against a large backlog ("at least N").
+const BACKLOG_COUNT_CAP = 1_000_000;
+
+/**
+ * Periodic status event for Axiom: how many customer entitlements are
+ * overdue-eligible right now, current queue depth, and how far behind "now"
+ * the scan position is.
+ */
+export const logResetBacklogGauge = async ({
+ ctx,
+ cursor,
+ sweepScanned,
+}: {
+ ctx: CronContext;
+ cursor: ResetScanCursor | null;
+ sweepScanned: number;
+}) => {
+ const [backlog, queueDepth] = await Promise.all([
+ customerEntitlementsRepo.countResetEligibleCustomerEntitlements({
+ db: ctx.db,
+ dueBefore: Date.now(),
+ cap: BACKLOG_COUNT_CAP,
+ }),
+ getBatchResetQueueDepth().catch(() => null),
+ ]);
+
+ ctx.logger.info("[reset-cus-ents-v2] backlog gauge", {
+ jobName: "reset-cus-ents-v2",
+ data: {
+ overdueEligibleCount: backlog.count,
+ overdueCountCapped: backlog.capped,
+ queueVisible: queueDepth?.visible ?? null,
+ queueInFlight: queueDepth?.inFlight ?? null,
+ sweepScanned,
+ cursorResetAt: cursor?.nextResetAt ?? null,
+ // How far behind "now" the scan position is, in ms.
+ scanLagMs: cursor ? Date.now() - cursor.nextResetAt : null,
+ },
+ });
+};
diff --git a/server/src/internal/balances/batchReset/resetCronQueryTag.ts b/server/src/internal/balances/batchReset/resetCronQueryTag.ts
new file mode 100644
index 0000000000..328cab633a
--- /dev/null
+++ b/server/src/internal/balances/batchReset/resetCronQueryTag.ts
@@ -0,0 +1,16 @@
+import { planetScaleTag } from "@/db/dbUtils.js";
+
+const RESET_CRON_WORKLOAD = "resetCronV2";
+
+type ResetCronQuery =
+ | "countEligible"
+ | "deleteRollovers"
+ | "expireEntitlements"
+ | "hydrateContext"
+ | "markInvoiceReset"
+ | "scanEligible"
+ | "updateBalances"
+ | "upsertRollovers";
+
+export const resetCronQueryTag = (query: ResetCronQuery) =>
+ planetScaleTag({ query: `reset:${query}`, workload: RESET_CRON_WORKLOAD });
diff --git a/server/src/internal/balances/batchReset/runResetLoopV2.ts b/server/src/internal/balances/batchReset/runResetLoopV2.ts
new file mode 100644
index 0000000000..9094e718fa
--- /dev/null
+++ b/server/src/internal/balances/batchReset/runResetLoopV2.ts
@@ -0,0 +1,213 @@
+import { ms } from "@autumn/shared";
+import * as Sentry from "@sentry/bun";
+import type { CronContext } from "@/cron/utils/CronContext.js";
+import type {
+ ResetEligibleCustomerEntitlementRow,
+ ResetScanCursor,
+} from "@/internal/customers/cusProducts/cusEnts/repos/getResetEligibleCustomerEntitlementsPage.js";
+import { customerEntitlementsRepo } from "@/internal/customers/cusProducts/cusEnts/repos/index.js";
+import {
+ getResetJobV2Config,
+ isResetJobV2Enabled,
+} from "@/internal/misc/resetJobV2/resetJobV2Store.js";
+import { isActiveSlot } from "@/queue/blueGreen/blueGreenGate.js";
+import {
+ waitForQueueBelowHighWater,
+ waitForQueueDrained,
+} from "./concurrency/batchResetScanGates.js";
+import { sleepWithAbort } from "./concurrency/sleepWithAbort.js";
+import { enqueueBatchResetTask } from "./enqueueBatchResetTask.js";
+import { logResetBacklogGauge } from "./logs/logResetBacklogGauge.js";
+import type { BatchResetCustomerEntitlementsV2Payload } from "./types.js";
+
+const JOB_NAME = "reset-cus-ents-v2";
+const IDLE_DELAY_MS = ms.seconds(5);
+const BACKLOG_GAUGE_INTERVAL_MS = ms.minutes(5);
+
+/** Progress of the sweep currently in flight. */
+type SweepState = {
+ cursor: ResetScanCursor | null;
+ dueBefore: number | null;
+ startedAt: number | null;
+ pages: number;
+ scanned: number;
+ messages: number;
+};
+
+const newSweepState = (): SweepState => ({
+ cursor: null,
+ dueBefore: null,
+ startedAt: null,
+ pages: 0,
+ scanned: 0,
+ messages: 0,
+});
+
+/** Chunks one scanned page into compact ID-only SQS payloads. */
+export const pageToBatchResetPayloads = ({
+ page,
+ workerBatchSize,
+}: {
+ page: ResetEligibleCustomerEntitlementRow[];
+ workerBatchSize: number;
+}): BatchResetCustomerEntitlementsV2Payload[] => {
+ const payloads: BatchResetCustomerEntitlementsV2Payload[] = [];
+
+ for (let i = 0; i < page.length; i += workerBatchSize) {
+ payloads.push({
+ customerEntitlementIds: page
+ .slice(i, i + workerBatchSize)
+ .map((row) => row.id),
+ });
+ }
+ return payloads;
+};
+
+const shouldRunPage = () =>
+ process.env.DISABLE_CRON !== "true" &&
+ isResetJobV2Enabled() &&
+ isActiveSlot({ serviceName: "cron" });
+
+/** Enqueues one scanned page and folds it into the sweep counters. */
+const enqueuePage = async ({
+ ctx,
+ page,
+ sweep,
+ workerBatchSize,
+}: {
+ ctx: CronContext;
+ page: ResetEligibleCustomerEntitlementRow[];
+ sweep: SweepState;
+ workerBatchSize: number;
+}) => {
+ const payloads = pageToBatchResetPayloads({ page, workerBatchSize });
+ for (const payload of payloads) {
+ await enqueueBatchResetTask({ payload, logger: ctx.logger });
+ }
+
+ const lastRow = page[page.length - 1];
+ sweep.cursor = { nextResetAt: lastRow.nextResetAt, id: lastRow.id };
+ sweep.pages++;
+ sweep.scanned += page.length;
+ sweep.messages += payloads.length;
+
+ ctx.logger.info("[reset-cus-ents-v2] page enqueued", {
+ jobName: JOB_NAME,
+ data: {
+ fetched: page.length,
+ messages: payloads.length,
+ dueBefore: sweep.dueBefore,
+ sweepPages: sweep.pages,
+ sweepScanned: sweep.scanned,
+ cursorResetAt: lastRow.nextResetAt,
+ cursorId: lastRow.id,
+ },
+ });
+};
+
+/** Gate B + completion event: wait for the workers to drain this sweep's
+ * messages, then report it (idle sweeps that scanned nothing stay silent). */
+const completeSweep = async ({
+ ctx,
+ signal,
+ sweep,
+}: {
+ ctx: CronContext;
+ signal: AbortSignal;
+ sweep: SweepState;
+}) => {
+ const barrierStartedAt = Date.now();
+ await waitForQueueDrained({ logger: ctx.logger, signal });
+
+ if (sweep.scanned === 0 || sweep.startedAt === null) return;
+
+ ctx.logger.info("[reset-cus-ents-v2] sweep completed", {
+ jobName: JOB_NAME,
+ data: {
+ sweepPages: sweep.pages,
+ sweepScanned: sweep.scanned,
+ sweepMessages: sweep.messages,
+ sweepDurationMs: Date.now() - sweep.startedAt,
+ drainWaitMs: Date.now() - barrierStartedAt,
+ dueBefore: sweep.dueBefore,
+ },
+ });
+};
+
+/**
+ * V2 reset cron: a continuous keyset sweep over overdue customer entitlements,
+ * fanned out to the batch reset queue.
+ *
+ * Flow per iteration: backlog gauge (rate-limited) -> Gate A (queue
+ * backpressure) -> scan one page -> enqueue. When a sweep drains, Gate B
+ * waits for the workers to finish before the cursor rewinds, so a new sweep
+ * can never re-enqueue rows that are still queued or in flight.
+ */
+export const runResetLoopV2 = async ({
+ ctx,
+ signal,
+}: {
+ ctx: CronContext;
+ signal: AbortSignal;
+}) => {
+ let sweep = newSweepState();
+ let lastGaugeAt = 0;
+
+ while (!signal.aborted) {
+ let delayMs = IDLE_DELAY_MS;
+
+ if (shouldRunPage()) {
+ try {
+ const { scanBatchSize, workerBatchSize, scanIntervalMs } =
+ getResetJobV2Config();
+
+ if (Date.now() - lastGaugeAt >= BACKLOG_GAUGE_INTERVAL_MS) {
+ lastGaugeAt = Date.now();
+ await logResetBacklogGauge({
+ ctx,
+ cursor: sweep.cursor,
+ sweepScanned: sweep.scanned,
+ });
+ }
+
+ // Gate A: don't scan further ahead than the workers can chew.
+ await waitForQueueBelowHighWater({ logger: ctx.logger, signal });
+ if (signal.aborted) break;
+
+ sweep.dueBefore ??= Date.now();
+ sweep.startedAt ??= Date.now();
+ const page =
+ await customerEntitlementsRepo.getResetEligibleCustomerEntitlementsPage(
+ {
+ db: ctx.db,
+ dueBefore: sweep.dueBefore,
+ cursor: sweep.cursor,
+ limit: scanBatchSize,
+ },
+ );
+
+ if (page.length > 0) {
+ await enqueuePage({ ctx, page, sweep, workerBatchSize });
+ }
+
+ if (page.length === scanBatchSize) {
+ delayMs = scanIntervalMs;
+ } else {
+ await completeSweep({ ctx, signal, sweep });
+ sweep = newSweepState();
+ }
+ } catch (error) {
+ sweep = newSweepState();
+ ctx.logger.error("[reset-cus-ents-v2] page failed", {
+ jobName: JOB_NAME,
+ err: error,
+ });
+ Sentry.captureException(error, {
+ extra: { context: "runResetLoopV2.fetchPage" },
+ });
+ }
+ }
+
+ await sleepWithAbort({ delayMs, signal });
+ }
+};
diff --git a/server/src/internal/balances/batchReset/setup/setupBatchResetContext.ts b/server/src/internal/balances/batchReset/setup/setupBatchResetContext.ts
new file mode 100644
index 0000000000..8ae12e4346
--- /dev/null
+++ b/server/src/internal/balances/batchReset/setup/setupBatchResetContext.ts
@@ -0,0 +1,101 @@
+import type { AppEnv } from "@autumn/shared";
+import type { DrizzleCli } from "@/db/initDrizzle.js";
+import type { Logger } from "@/external/logtail/logtailUtils.js";
+import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
+import type { ResetContextCustomerEntitlement } from "@/internal/customers/cusProducts/cusEnts/repos/getResetContextByIds.js";
+import { customerEntitlementsRepo } from "@/internal/customers/cusProducts/cusEnts/repos/index.js";
+import { createWorkerContext } from "@/queue/createWorkerContext.js";
+import type {
+ BatchResetContext,
+ BatchResetCustomerEntitlementsV2Payload,
+ BatchResetGroup,
+} from "../types.js";
+
+const orgEnvKey = ({ orgId, env }: { orgId: string; env: string }) =>
+ `${orgId}:${env}`;
+
+/**
+ * Builds one worker context per unique org+env in the batch (each context
+ * loads the org + features once). Orgs deleted since queueing are skipped.
+ */
+const fetchUniqueOrgContexts = async ({
+ db,
+ logger,
+ customerEntitlements,
+}: {
+ db: DrizzleCli;
+ logger: Logger;
+ customerEntitlements: ResetContextCustomerEntitlement[];
+}): Promise]