license_manager 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +152 -0
  3. data/README.md +3 -3
  4. data/docs/BillingPortalSession.md +18 -0
  5. data/docs/CancelLicense.md +22 -0
  6. data/docs/CancellationReason.md +15 -0
  7. data/docs/CancellationStats.md +30 -0
  8. data/docs/CancellationStatsByReasonInner.md +20 -0
  9. data/docs/ConnectStripe.md +1 -1
  10. data/docs/CreateBillingPortalSession.md +18 -0
  11. data/docs/CreateIntegrationMapping.md +1 -1
  12. data/docs/CreateLicense.md +2 -0
  13. data/docs/CreatePolicy.md +3 -1
  14. data/docs/CreateProductWithPolicy.md +1 -1
  15. data/docs/CreateToken.md +1 -1
  16. data/docs/EventActorOneOf.md +1 -1
  17. data/docs/Integration.md +11 -1
  18. data/docs/IntegrationMapping.md +2 -0
  19. data/docs/IntegrationMappingRecurring.md +20 -0
  20. data/docs/IntegrationsApi.md +74 -0
  21. data/docs/License.md +12 -0
  22. data/docs/LicenseSubscription.md +34 -0
  23. data/docs/LicensesApi.md +80 -3
  24. data/docs/Machine.md +3 -1
  25. data/docs/MachinesApi.md +1 -1
  26. data/docs/Mrr.md +26 -0
  27. data/docs/MrrByIntervalInner.md +24 -0
  28. data/docs/MrrByPolicyInner.md +22 -0
  29. data/docs/Policy.md +2 -0
  30. data/docs/PolicySettings.md +3 -1
  31. data/docs/PortalApi.md +80 -1
  32. data/docs/PortalLink.md +2 -2
  33. data/docs/Product.md +2 -0
  34. data/docs/ProductStats.md +8 -4
  35. data/docs/ProductsApi.md +2 -2
  36. data/docs/Revenue.md +10 -2
  37. data/docs/StripeIntegration.md +1 -1
  38. data/docs/StripePermission.md +5 -1
  39. data/docs/StripePrice.md +2 -0
  40. data/docs/StripePriceRecurring.md +20 -0
  41. data/docs/SubscriptionStats.md +38 -0
  42. data/docs/UpdateIntegration.md +20 -0
  43. data/docs/UpdateLicense.md +3 -1
  44. data/docs/UpdatePolicy.md +3 -1
  45. data/docs/UpdateProduct.md +3 -1
  46. data/lib/license_manager/api/integrations_api.rb +74 -0
  47. data/lib/license_manager/api/licenses_api.rb +80 -6
  48. data/lib/license_manager/api/machines_api.rb +3 -3
  49. data/lib/license_manager/api/portal_api.rb +72 -2
  50. data/lib/license_manager/api/products_api.rb +4 -4
  51. data/lib/license_manager/models/billing_portal_session.rb +178 -0
  52. data/lib/license_manager/models/cancel_license.rb +238 -0
  53. data/lib/license_manager/models/cancellation_reason.rb +45 -0
  54. data/lib/license_manager/models/cancellation_stats.rb +347 -0
  55. data/lib/license_manager/models/cancellation_stats_by_reason_inner.rb +225 -0
  56. data/lib/license_manager/models/connect_stripe.rb +1 -1
  57. data/lib/license_manager/models/create_billing_portal_session.rb +161 -0
  58. data/lib/license_manager/models/create_integration_mapping.rb +1 -1
  59. data/lib/license_manager/models/create_license.rb +12 -1
  60. data/lib/license_manager/models/create_policy.rb +40 -5
  61. data/lib/license_manager/models/create_product_with_policy.rb +1 -1
  62. data/lib/license_manager/models/create_token.rb +1 -1
  63. data/lib/license_manager/models/event_type.rb +7 -1
  64. data/lib/license_manager/models/integration.rb +136 -1
  65. data/lib/license_manager/models/integration_mapping.rb +13 -1
  66. data/lib/license_manager/models/integration_mapping_recurring.rb +228 -0
  67. data/lib/license_manager/models/license.rb +91 -1
  68. data/lib/license_manager/models/license_status.rb +2 -1
  69. data/lib/license_manager/models/license_subscription.rb +388 -0
  70. data/lib/license_manager/models/machine.rb +17 -4
  71. data/lib/license_manager/models/mrr.rb +291 -0
  72. data/lib/license_manager/models/mrr_by_interval_inner.rb +279 -0
  73. data/lib/license_manager/models/mrr_by_policy_inner.rb +229 -0
  74. data/lib/license_manager/models/policy.rb +14 -1
  75. data/lib/license_manager/models/policy_settings.rb +40 -5
  76. data/lib/license_manager/models/portal_link.rb +1 -1
  77. data/lib/license_manager/models/product.rb +28 -1
  78. data/lib/license_manager/models/product_stats.rb +31 -5
  79. data/lib/license_manager/models/revenue.rb +112 -4
  80. data/lib/license_manager/models/stripe_integration.rb +1 -1
  81. data/lib/license_manager/models/stripe_permission.rb +58 -4
  82. data/lib/license_manager/models/stripe_price.rb +13 -1
  83. data/lib/license_manager/models/stripe_price_recurring.rb +228 -0
  84. data/lib/license_manager/models/subscription_stats.rb +456 -0
  85. data/lib/license_manager/models/update_integration.rb +199 -0
  86. data/lib/license_manager/models/update_license.rb +16 -5
  87. data/lib/license_manager/models/update_policy.rb +40 -5
  88. data/lib/license_manager/models/update_product.rb +14 -4
  89. data/lib/license_manager/version.rb +1 -1
  90. data/lib/license_manager.rb +14 -0
  91. data/openapi.json +1146 -59
  92. data/spec/api/integrations_api_spec.rb +13 -0
  93. data/spec/api/licenses_api_spec.rb +16 -3
  94. data/spec/api/machines_api_spec.rb +1 -1
  95. data/spec/api/portal_api_spec.rb +14 -1
  96. data/spec/api/products_api_spec.rb +2 -2
  97. data/spec/models/billing_portal_session_spec.rb +36 -0
  98. data/spec/models/cancel_license_spec.rb +48 -0
  99. data/spec/models/cancellation_reason_spec.rb +30 -0
  100. data/spec/models/cancellation_stats_by_reason_inner_spec.rb +42 -0
  101. data/spec/models/cancellation_stats_spec.rb +72 -0
  102. data/spec/models/create_billing_portal_session_spec.rb +36 -0
  103. data/spec/models/create_license_spec.rb +6 -0
  104. data/spec/models/create_policy_spec.rb +6 -0
  105. data/spec/models/create_token_spec.rb +1 -1
  106. data/spec/models/integration_mapping_recurring_spec.rb +46 -0
  107. data/spec/models/integration_mapping_spec.rb +6 -0
  108. data/spec/models/integration_spec.rb +30 -0
  109. data/spec/models/license_spec.rb +36 -0
  110. data/spec/models/license_subscription_spec.rb +88 -0
  111. data/spec/models/machine_spec.rb +7 -1
  112. data/spec/models/mrr_by_interval_inner_spec.rb +58 -0
  113. data/spec/models/mrr_by_policy_inner_spec.rb +48 -0
  114. data/spec/models/mrr_spec.rb +60 -0
  115. data/spec/models/policy_settings_spec.rb +6 -0
  116. data/spec/models/policy_spec.rb +6 -0
  117. data/spec/models/product_spec.rb +6 -0
  118. data/spec/models/product_stats_spec.rb +12 -0
  119. data/spec/models/revenue_spec.rb +24 -0
  120. data/spec/models/stripe_permission_spec.rb +12 -0
  121. data/spec/models/stripe_price_recurring_spec.rb +46 -0
  122. data/spec/models/stripe_price_spec.rb +6 -0
  123. data/spec/models/subscription_stats_spec.rb +96 -0
  124. data/spec/models/token_spec.rb +1 -1
  125. data/spec/models/update_integration_spec.rb +42 -0
  126. data/spec/models/update_license_spec.rb +6 -0
  127. data/spec/models/update_policy_spec.rb +6 -0
  128. data/spec/models/update_product_spec.rb +6 -0
  129. metadata +57 -1
data/openapi.json CHANGED
@@ -31,7 +31,7 @@
31
31
  "type": "http",
32
32
  "scheme": "bearer",
33
33
  "bearerFormat": "JWT",
34
- "description": "Short-lived token scoped to a customer, license or machine, signed with HS256. Its claims:\n\n- `typ`: `customer`, `license` or `machine`, the type of resource the token is scoped to.\n- `sub`: id of that resource.\n- `scope`: space separated permissions, among `customer:read`, `customer:write`, `license:read`, `license:write`, `machine:read` and `machine:write`. A write permission implies the matching read one. Without `scope`, the token can't do anything.\n- `exp`: expiration time.\n\nOnly the endpoints that say so accept tokens: the others answer `403` with `token_not_allowed`, as do the endpoints that don't accept its type. A token without the endpoint's permission answers `403` with `insufficient_scope`. The token reaches the resource it's scoped to, the ones below it and the ones above it: a license token reaches its customer and machines, but not the customer's other licenses. Resources it doesn't reach answer `404`.\n\nA token can't be revoked: it works until it expires, unless the resource it's scoped to is deleted, which makes it answer `401` with `invalid_token`. Tokens are created by `createCustomerToken`, `createLicenseToken` and `createMachineToken`, with an API key or a session, or with a token for the resources below it: a customer token creates license and machine tokens, a license token machine tokens. Portal links (`createPortalLink`, `requestPortalLink`) hold customer tokens too."
34
+ "description": "Short-lived token scoped to a customer, license or machine, signed with HS256. Its claims:\n\n- `typ`: `customer`, `license` or `machine`, the type of resource the token is scoped to.\n- `sub`: id of that resource.\n- `scope`: space separated permissions, among `customer:read`, `customer:write`, `license:read`, `license:write`, `license:cancel`, `machine:read` and `machine:write`. A write permission implies the other ones of its resource: `license:write` implies `license:read` and `license:cancel`. Without `scope`, the token can't do anything.\n- `exp`: expiration time.\n\nOnly the endpoints that say so accept tokens: the others answer `403` with `token_not_allowed`, as do the endpoints that don't accept its type. A token without the endpoint's permission answers `403` with `insufficient_scope`. The token reaches the resource it's scoped to, the ones below it and the ones above it: a license token reaches its customer and machines, but not the customer's other licenses. Resources it doesn't reach answer `404`.\n\nA token can't be revoked: it works until it expires, unless the resource it's scoped to is deleted, which makes it answer `401` with `invalid_token`. Tokens are created by `createCustomerToken`, `createLicenseToken` and `createMachineToken`, with an API key or a session, or with a token for the resources below it: a customer token creates license and machine tokens, a license token machine tokens. Portal links (`createPortalLink`, `requestPortalLink`) hold customer tokens too."
35
35
  }
36
36
  },
37
37
  "schemas": {
@@ -373,6 +373,11 @@
373
373
  "portal": {
374
374
  "$ref": "#/components/schemas/PortalSettings"
375
375
  },
376
+ "autoMachineChange": {
377
+ "type": "boolean",
378
+ "description": "Whether a new machine validating a license already on its max machines takes the place of the license's machine validated the longest ago, rather than being refused with `machine_limit_reached`. When the replaced machine is validated again, it takes its place back and the machine that replaced it is locked. See `validateKey`.",
379
+ "example": true
380
+ },
376
381
  "createdAt": {
377
382
  "type": "string",
378
383
  "format": "date-time",
@@ -391,6 +396,7 @@
391
396
  "isTestFromProductId",
392
397
  "logoUrl",
393
398
  "portal",
399
+ "autoMachineChange",
394
400
  "createdAt",
395
401
  "updatedAt"
396
402
  ]
@@ -498,7 +504,7 @@
498
504
  },
499
505
  "isTestFromProductId": {
500
506
  "type": "string",
501
- "description": "Creates a test product: a sandbox of this live product of the organization, starting with copies of its policies, releases and portal settings.",
507
+ "description": "Creates a test product: a sandbox of this live product of the organization, starting with copies of its policies, releases, portal settings and `autoMachineChange`.",
502
508
  "example": "x7Kp2QaZ"
503
509
  }
504
510
  }
@@ -589,6 +595,16 @@
589
595
  }
590
596
  ],
591
597
  "description": "Latest version of the product the licenses created with this policy can use. `null` allows every version. Defaults to `null`.\n\n- `fixed`: up to a hard-coded `version`. `\"2\"` allows every 2.y.z version: its minor versions and bug fixes. `\"2.3\"` allows every 2.3.z version: its bug fixes.\n- `lock`: every version released during some time after the license is created, then only the updates of the version reached by then. The major version locks `majorAfter` the creation of the license, then the license only gets the minor versions and bug fixes of its major version. The minor version optionally locks `minorAfter` it, never sooner than the major version, then the license only gets the bug fixes of its minor version. `{ \"type\": \"lock\", \"majorAfter\": { \"value\": 1, \"unit\": \"months\" }, \"minorAfter\": { \"value\": 12, \"unit\": \"months\" } }` allows every version released during the month after the license is created, then the minor versions and bug fixes of the major version reached by then until the end of the first year, then only the bug fixes of the minor version reached by then. `majorAfter` at 0 allows minor versions only, and both at 0 allows bug fixes only: no updates.\n\nBug fixes are never locked: a hard-coded version has no patch number, like `\"2.3.1\"`, and a lock only locks major and minor versions."
598
+ },
599
+ "moneyBackGuaranteeDays": {
600
+ "type": [
601
+ "integer",
602
+ "null"
603
+ ],
604
+ "exclusiveMinimum": 0,
605
+ "maximum": 3650,
606
+ "description": "Number of days after their creation during which customers can cancel the licenses created with this policy, from the customer portal or with `cancelLicense`, which refunds their Stripe payment. Each license keeps the end of its own period as `cancellableUntil`. `null` for no money back guarantee. Defaults to `null`.",
607
+ "example": 30
592
608
  }
593
609
  }
594
610
  },
@@ -819,6 +835,11 @@
819
835
  ]
820
836
  }
821
837
  }
838
+ },
839
+ "autoMachineChange": {
840
+ "type": "boolean",
841
+ "description": "Whether a new machine validating a license already on its max machines takes the place of the license's machine validated the longest ago, rather than being refused with `machine_limit_reached`. When the replaced machine is validated again, it takes its place back and the machine that replaced it is locked. See `validateKey`.",
842
+ "example": true
822
843
  }
823
844
  }
824
845
  },
@@ -845,7 +866,7 @@
845
866
  },
846
867
  "activeLicenseCount": {
847
868
  "type": "integer",
848
- "description": "Number of licenses that haven't expired, and that a machine validated in the last 90 days.",
869
+ "description": "Number of licenses that haven't expired nor were canceled, and that a machine validated in the last 90 days.",
849
870
  "example": 87
850
871
  },
851
872
  "newLicenseCount": {
@@ -855,12 +876,12 @@
855
876
  },
856
877
  "expiringLicenseCount": {
857
878
  "type": "integer",
858
- "description": "Number of licenses expiring in the next 30 days.",
879
+ "description": "Number of licenses expiring in the next 30 days, canceled ones left out.",
859
880
  "example": 3
860
881
  },
861
882
  "expiredLicenseCount": {
862
883
  "type": "integer",
863
- "description": "Number of licenses that expired in the last 30 days.",
884
+ "description": "Number of licenses that expired in the last 30 days, canceled ones left out.",
864
885
  "example": 2
865
886
  },
866
887
  "activeMachineCount": {
@@ -875,7 +896,7 @@
875
896
  },
876
897
  "seatCount": {
877
898
  "type": "integer",
878
- "description": "Sum of the maximum numbers of machines of the licenses that haven't expired. Licenses without a maximum are left out.",
899
+ "description": "Sum of the maximum numbers of machines of the licenses that haven't expired nor were canceled. Licenses without a maximum are left out.",
879
900
  "example": 200
880
901
  },
881
902
  "usedSeatCount": {
@@ -972,6 +993,28 @@
972
993
  },
973
994
  "description": "Number of licenses of each policy created each day and each month, for the policies with licenses created over the covered months."
974
995
  },
996
+ "cancellation": {
997
+ "anyOf": [
998
+ {
999
+ "$ref": "#/components/schemas/CancellationStats"
1000
+ },
1001
+ {
1002
+ "type": "null"
1003
+ }
1004
+ ],
1005
+ "description": "Cancellations of the licenses with a money back guarantee. `null` when no license of the product has one."
1006
+ },
1007
+ "subscriptions": {
1008
+ "anyOf": [
1009
+ {
1010
+ "$ref": "#/components/schemas/SubscriptionStats"
1011
+ },
1012
+ {
1013
+ "type": "null"
1014
+ }
1015
+ ],
1016
+ "description": "Subscriptions of the product. `null` when it has never had one."
1017
+ },
975
1018
  "revenue": {
976
1019
  "type": "array",
977
1020
  "items": {
@@ -1079,12 +1122,297 @@
1079
1122
  "newLicensesByDay",
1080
1123
  "newLicensesByMonth",
1081
1124
  "newLicensesByPolicy",
1125
+ "cancellation",
1126
+ "subscriptions",
1082
1127
  "revenue",
1083
1128
  "activationsByVersion",
1084
1129
  "activeMachinesByVersion",
1085
1130
  "activeMachinesByPlatform"
1086
1131
  ]
1087
1132
  },
1133
+ "CancellationStats": {
1134
+ "type": "object",
1135
+ "properties": {
1136
+ "licenseCount": {
1137
+ "type": "integer",
1138
+ "description": "Number of licenses with a money back guarantee: a `cancellableUntil`.",
1139
+ "example": 80
1140
+ },
1141
+ "canceledLicenseCount": {
1142
+ "type": "integer",
1143
+ "description": "Number of them that were canceled.",
1144
+ "example": 4
1145
+ },
1146
+ "cancellableLicenseCount": {
1147
+ "type": "integer",
1148
+ "description": "Number of them whose money back guarantee hasn't ended, and that weren't canceled: they can still be.",
1149
+ "example": 12
1150
+ },
1151
+ "rate": {
1152
+ "type": "number",
1153
+ "description": "Share of them that were canceled, from 0 to 1: `canceledLicenseCount` over `licenseCount`.",
1154
+ "example": 0.05
1155
+ },
1156
+ "canceledByDay": {
1157
+ "type": "array",
1158
+ "items": {
1159
+ "type": "integer"
1160
+ },
1161
+ "description": "Number of them canceled each day, aligned on `days`, on the day of their cancellation.",
1162
+ "example": [
1163
+ 0,
1164
+ 1,
1165
+ 0
1166
+ ]
1167
+ },
1168
+ "canceledByMonth": {
1169
+ "type": "array",
1170
+ "items": {
1171
+ "type": "integer"
1172
+ },
1173
+ "description": "Number of them canceled each month, aligned on `months`, on the month of their cancellation.",
1174
+ "example": [
1175
+ 1,
1176
+ 0,
1177
+ 2
1178
+ ]
1179
+ },
1180
+ "byReason": {
1181
+ "type": "array",
1182
+ "items": {
1183
+ "type": "object",
1184
+ "properties": {
1185
+ "reason": {
1186
+ "$ref": "#/components/schemas/CancellationReason"
1187
+ },
1188
+ "count": {
1189
+ "type": "integer",
1190
+ "example": 2
1191
+ }
1192
+ },
1193
+ "required": [
1194
+ "reason",
1195
+ "count"
1196
+ ]
1197
+ },
1198
+ "description": "Number of them canceled for each reason, from the most given one. Reasons never given are left out."
1199
+ }
1200
+ },
1201
+ "required": [
1202
+ "licenseCount",
1203
+ "canceledLicenseCount",
1204
+ "cancellableLicenseCount",
1205
+ "rate",
1206
+ "canceledByDay",
1207
+ "canceledByMonth",
1208
+ "byReason"
1209
+ ],
1210
+ "description": "Cancellations of the licenses with a money back guarantee. Licenses canceled without one are left out."
1211
+ },
1212
+ "CancellationReason": {
1213
+ "type": "string",
1214
+ "enum": [
1215
+ "too_expensive",
1216
+ "missing_features",
1217
+ "not_working",
1218
+ "switched",
1219
+ "unused",
1220
+ "other"
1221
+ ],
1222
+ "description": "- `too_expensive`: too expensive.\n- `missing_features`: missing features i need.\n- `not_working`: doesn't work as expected.\n- `switched`: found an alternative.\n- `unused`: no longer needed.\n- `other`: other.",
1223
+ "example": "too_expensive"
1224
+ },
1225
+ "SubscriptionStats": {
1226
+ "type": "object",
1227
+ "properties": {
1228
+ "activeCount": {
1229
+ "type": "integer",
1230
+ "description": "Number of ongoing subscriptions: `active`, `trialing` or `past_due`.",
1231
+ "example": 120
1232
+ },
1233
+ "trialingCount": {
1234
+ "type": "integer",
1235
+ "example": 4
1236
+ },
1237
+ "pastDueCount": {
1238
+ "type": "integer",
1239
+ "description": "Number of them whose renewal failed, as Stripe retries it: their licenses stay valid for the integration's grace days.",
1240
+ "example": 3
1241
+ },
1242
+ "cancelingCount": {
1243
+ "type": "integer",
1244
+ "description": "Number of them set to end at the end of their period.",
1245
+ "example": 5
1246
+ },
1247
+ "newCount": {
1248
+ "type": "integer",
1249
+ "description": "Number of subscriptions started in the last 30 days.",
1250
+ "example": 14
1251
+ },
1252
+ "endedCount": {
1253
+ "type": "integer",
1254
+ "description": "Number of subscriptions that ended in the last 30 days.",
1255
+ "example": 3
1256
+ },
1257
+ "churnRate": {
1258
+ "type": "number",
1259
+ "description": "Share of the subscriptions ongoing 30 days ago that ended since, from 0 to 1.",
1260
+ "example": 0.025
1261
+ },
1262
+ "newByMonth": {
1263
+ "type": "array",
1264
+ "items": {
1265
+ "type": "integer"
1266
+ },
1267
+ "description": "Number of subscriptions started each month, aligned on `months`.",
1268
+ "example": [
1269
+ 3,
1270
+ 5,
1271
+ 8
1272
+ ]
1273
+ },
1274
+ "endedByMonth": {
1275
+ "type": "array",
1276
+ "items": {
1277
+ "type": "integer"
1278
+ },
1279
+ "description": "Number of subscriptions that ended each month, aligned on `months`.",
1280
+ "example": [
1281
+ 0,
1282
+ 1,
1283
+ 2
1284
+ ]
1285
+ },
1286
+ "activeByMonth": {
1287
+ "type": "array",
1288
+ "items": {
1289
+ "type": "integer"
1290
+ },
1291
+ "description": "Number of ongoing subscriptions at the end of each month, aligned on `months`, today for this month.",
1292
+ "example": [
1293
+ 90,
1294
+ 104,
1295
+ 120
1296
+ ]
1297
+ },
1298
+ "mrr": {
1299
+ "type": "array",
1300
+ "items": {
1301
+ "$ref": "#/components/schemas/Mrr"
1302
+ },
1303
+ "description": "MRR in each currency, from the highest number of subscriptions. Amounts aren't converted between currencies."
1304
+ }
1305
+ },
1306
+ "required": [
1307
+ "activeCount",
1308
+ "trialingCount",
1309
+ "pastDueCount",
1310
+ "cancelingCount",
1311
+ "newCount",
1312
+ "endedCount",
1313
+ "churnRate",
1314
+ "newByMonth",
1315
+ "endedByMonth",
1316
+ "activeByMonth",
1317
+ "mrr"
1318
+ ],
1319
+ "description": "Subscriptions to recurring Stripe prices, each with its subscription license."
1320
+ },
1321
+ "Mrr": {
1322
+ "type": "object",
1323
+ "properties": {
1324
+ "currency": {
1325
+ "type": "string",
1326
+ "example": "USD"
1327
+ },
1328
+ "mrr": {
1329
+ "type": "number",
1330
+ "description": "Monthly recurring revenue: the price of every ongoing subscription in the currency, times its quantity, brought to one month (a year counts for a twelfth), before taxes and discounts.",
1331
+ "example": 1240
1332
+ },
1333
+ "byInterval": {
1334
+ "type": "array",
1335
+ "items": {
1336
+ "type": "object",
1337
+ "properties": {
1338
+ "interval": {
1339
+ "type": "string",
1340
+ "enum": [
1341
+ "day",
1342
+ "week",
1343
+ "month",
1344
+ "year"
1345
+ ]
1346
+ },
1347
+ "intervalCount": {
1348
+ "type": "integer",
1349
+ "example": 1
1350
+ },
1351
+ "subscriptionCount": {
1352
+ "type": "integer",
1353
+ "example": 80
1354
+ },
1355
+ "mrr": {
1356
+ "type": "number",
1357
+ "example": 800
1358
+ }
1359
+ },
1360
+ "required": [
1361
+ "interval",
1362
+ "intervalCount",
1363
+ "subscriptionCount",
1364
+ "mrr"
1365
+ ]
1366
+ },
1367
+ "description": "Ongoing subscriptions and MRR of each billing period, like monthly and yearly, from the highest MRR."
1368
+ },
1369
+ "byPolicy": {
1370
+ "type": "array",
1371
+ "items": {
1372
+ "type": "object",
1373
+ "properties": {
1374
+ "policyId": {
1375
+ "type": "string",
1376
+ "example": "p4Rt8LmQ"
1377
+ },
1378
+ "subscriptionCount": {
1379
+ "type": "integer",
1380
+ "example": 80
1381
+ },
1382
+ "mrr": {
1383
+ "type": "number",
1384
+ "example": 800
1385
+ }
1386
+ },
1387
+ "required": [
1388
+ "policyId",
1389
+ "subscriptionCount",
1390
+ "mrr"
1391
+ ]
1392
+ },
1393
+ "description": "MRR of each policy, from the highest."
1394
+ },
1395
+ "byMonth": {
1396
+ "type": "array",
1397
+ "items": {
1398
+ "type": "number"
1399
+ },
1400
+ "description": "MRR at the end of each month, aligned on `months`, today for this month, at the current prices of the subscriptions.",
1401
+ "example": [
1402
+ 900,
1403
+ 1100,
1404
+ 1240
1405
+ ]
1406
+ }
1407
+ },
1408
+ "required": [
1409
+ "currency",
1410
+ "mrr",
1411
+ "byInterval",
1412
+ "byPolicy",
1413
+ "byMonth"
1414
+ ]
1415
+ },
1088
1416
  "Revenue": {
1089
1417
  "type": "object",
1090
1418
  "properties": {
@@ -1095,14 +1423,34 @@
1095
1423
  },
1096
1424
  "licenseCount": {
1097
1425
  "type": "integer",
1098
- "description": "Number of licenses sold in the currency, free ones included.",
1426
+ "description": "Number of licenses sold once in the currency, free ones included, canceled ones and subscription licenses left out.",
1099
1427
  "example": 42
1100
1428
  },
1101
1429
  "total": {
1102
1430
  "type": "number",
1103
- "description": "Sum of the prices of the licenses sold in the currency, in major units.",
1431
+ "description": "Revenue in the currency, in major units: the prices of the licenses sold once, canceled ones left out, plus the payments of the subscriptions, `subscriptionTotal`.",
1104
1432
  "example": 2099.58
1105
1433
  },
1434
+ "subscriptionTotal": {
1435
+ "type": "number",
1436
+ "description": "Sum of the payments of the subscription licenses in the currency, before taxes, in major units.",
1437
+ "example": 480
1438
+ },
1439
+ "paymentCount": {
1440
+ "type": "integer",
1441
+ "description": "Number of payments of the subscription licenses in the currency.",
1442
+ "example": 48
1443
+ },
1444
+ "canceledLicenseCount": {
1445
+ "type": "integer",
1446
+ "description": "Number of canceled licenses sold once in the currency.",
1447
+ "example": 2
1448
+ },
1449
+ "canceledTotal": {
1450
+ "type": "number",
1451
+ "description": "Sum of the prices of the canceled licenses sold once in the currency, in major units: revenue lost to cancellations.",
1452
+ "example": 99.98
1453
+ },
1106
1454
  "thisMonth": {
1107
1455
  "type": "number",
1108
1456
  "description": "Revenue of the current month, in UTC.",
@@ -1189,6 +1537,10 @@
1189
1537
  "currency",
1190
1538
  "licenseCount",
1191
1539
  "total",
1540
+ "subscriptionTotal",
1541
+ "paymentCount",
1542
+ "canceledLicenseCount",
1543
+ "canceledTotal",
1192
1544
  "thisMonth",
1193
1545
  "lastMonth",
1194
1546
  "last30Days",
@@ -1196,7 +1548,7 @@
1196
1548
  "byMonth",
1197
1549
  "byPolicy"
1198
1550
  ],
1199
- "description": "Revenue of the licenses sold in one currency, each license counting on the day it was created. Licenses without a price are left out."
1551
+ "description": "Revenue in one currency: the licenses sold once, each counting on the day it was created, and the payments of the subscription licenses, each counting on the day it was paid, with the license's policy. Licenses without a price are left out, and so are canceled ones, but in `canceledLicenseCount` and `canceledTotal`."
1200
1552
  },
1201
1553
  "PolicyList": {
1202
1554
  "type": "object",
@@ -1319,6 +1671,14 @@
1319
1671
  ],
1320
1672
  "description": "Latest version of the product the licenses created with this policy can use. `null` when they can use every version.\n\n- `fixed`: up to a hard-coded `version`. `\"2\"` allows every 2.y.z version: its minor versions and bug fixes. `\"2.3\"` allows every 2.3.z version: its bug fixes.\n- `lock`: every version released during some time after the license is created, then only the updates of the version reached by then. The major version locks `majorAfter` the creation of the license, then the license only gets the minor versions and bug fixes of its major version. The minor version optionally locks `minorAfter` it, never sooner than the major version, then the license only gets the bug fixes of its minor version. `{ \"type\": \"lock\", \"majorAfter\": { \"value\": 1, \"unit\": \"months\" }, \"minorAfter\": { \"value\": 12, \"unit\": \"months\" } }` allows every version released during the month after the license is created, then the minor versions and bug fixes of the major version reached by then until the end of the first year, then only the bug fixes of the minor version reached by then. `majorAfter` at 0 allows minor versions only, and both at 0 allows bug fixes only: no updates.\n\nBug fixes are never locked: a hard-coded version has no patch number, like `\"2.3.1\"`, and a lock only locks major and minor versions."
1321
1673
  },
1674
+ "moneyBackGuaranteeDays": {
1675
+ "type": [
1676
+ "integer",
1677
+ "null"
1678
+ ],
1679
+ "description": "Number of days after their creation during which customers can cancel the licenses created with this policy, from the customer portal or with `cancelLicense`. `null` when they can't.",
1680
+ "example": 30
1681
+ },
1322
1682
  "licenseCount": {
1323
1683
  "type": "integer",
1324
1684
  "description": "Number of licenses following this policy.",
@@ -1345,6 +1705,7 @@
1345
1705
  "maxMachines",
1346
1706
  "maxOfflineDurationSeconds",
1347
1707
  "maxVersion",
1708
+ "moneyBackGuaranteeDays",
1348
1709
  "licenseCount",
1349
1710
  "createdAt",
1350
1711
  "updatedAt"
@@ -1453,6 +1814,16 @@
1453
1814
  }
1454
1815
  ],
1455
1816
  "description": "Latest version of the product the licenses created with this policy can use. `null` allows every version. Defaults to `null`.\n\n- `fixed`: up to a hard-coded `version`. `\"2\"` allows every 2.y.z version: its minor versions and bug fixes. `\"2.3\"` allows every 2.3.z version: its bug fixes.\n- `lock`: every version released during some time after the license is created, then only the updates of the version reached by then. The major version locks `majorAfter` the creation of the license, then the license only gets the minor versions and bug fixes of its major version. The minor version optionally locks `minorAfter` it, never sooner than the major version, then the license only gets the bug fixes of its minor version. `{ \"type\": \"lock\", \"majorAfter\": { \"value\": 1, \"unit\": \"months\" }, \"minorAfter\": { \"value\": 12, \"unit\": \"months\" } }` allows every version released during the month after the license is created, then the minor versions and bug fixes of the major version reached by then until the end of the first year, then only the bug fixes of the minor version reached by then. `majorAfter` at 0 allows minor versions only, and both at 0 allows bug fixes only: no updates.\n\nBug fixes are never locked: a hard-coded version has no patch number, like `\"2.3.1\"`, and a lock only locks major and minor versions."
1817
+ },
1818
+ "moneyBackGuaranteeDays": {
1819
+ "type": [
1820
+ "integer",
1821
+ "null"
1822
+ ],
1823
+ "exclusiveMinimum": 0,
1824
+ "maximum": 3650,
1825
+ "description": "Number of days after their creation during which customers can cancel the licenses created with this policy, from the customer portal or with `cancelLicense`, which refunds their Stripe payment. Each license keeps the end of its own period as `cancellableUntil`. `null` for no money back guarantee. Defaults to `null`.",
1826
+ "example": 30
1456
1827
  }
1457
1828
  },
1458
1829
  "required": [
@@ -1562,6 +1933,16 @@
1562
1933
  }
1563
1934
  ],
1564
1935
  "description": "Latest version of the product the licenses created with this policy can use. `null` allows every version. Defaults to `null`.\n\n- `fixed`: up to a hard-coded `version`. `\"2\"` allows every 2.y.z version: its minor versions and bug fixes. `\"2.3\"` allows every 2.3.z version: its bug fixes.\n- `lock`: every version released during some time after the license is created, then only the updates of the version reached by then. The major version locks `majorAfter` the creation of the license, then the license only gets the minor versions and bug fixes of its major version. The minor version optionally locks `minorAfter` it, never sooner than the major version, then the license only gets the bug fixes of its minor version. `{ \"type\": \"lock\", \"majorAfter\": { \"value\": 1, \"unit\": \"months\" }, \"minorAfter\": { \"value\": 12, \"unit\": \"months\" } }` allows every version released during the month after the license is created, then the minor versions and bug fixes of the major version reached by then until the end of the first year, then only the bug fixes of the minor version reached by then. `majorAfter` at 0 allows minor versions only, and both at 0 allows bug fixes only: no updates.\n\nBug fixes are never locked: a hard-coded version has no patch number, like `\"2.3.1\"`, and a lock only locks major and minor versions."
1936
+ },
1937
+ "moneyBackGuaranteeDays": {
1938
+ "type": [
1939
+ "integer",
1940
+ "null"
1941
+ ],
1942
+ "exclusiveMinimum": 0,
1943
+ "maximum": 3650,
1944
+ "description": "Number of days after their creation during which customers can cancel the licenses created with this policy, from the customer portal or with `cancelLicense`, which refunds their Stripe payment. Each license keeps the end of its own period as `cancellableUntil`. `null` for no money back guarantee. Defaults to `null`.",
1945
+ "example": 30
1565
1946
  }
1566
1947
  }
1567
1948
  },
@@ -1796,6 +2177,57 @@
1796
2177
  "status": {
1797
2178
  "$ref": "#/components/schemas/LicenseStatus"
1798
2179
  },
2180
+ "cancellableUntil": {
2181
+ "type": [
2182
+ "string",
2183
+ "null"
2184
+ ],
2185
+ "format": "date-time",
2186
+ "description": "End of the money back guarantee: until then, the customer can cancel the license from the customer portal, or with a token. Set from the policy's `moneyBackGuaranteeDays` when the license is created. `null` when the customer can't cancel it.",
2187
+ "example": "2026-02-14T09:30:00.000Z"
2188
+ },
2189
+ "canceledAt": {
2190
+ "type": [
2191
+ "string",
2192
+ "null"
2193
+ ],
2194
+ "format": "date-time",
2195
+ "description": "When the license was canceled: validating its key fails since. `null` when it wasn't.",
2196
+ "example": "2026-01-20T16:12:00.000Z"
2197
+ },
2198
+ "cancellationReason": {
2199
+ "allOf": [
2200
+ {
2201
+ "$ref": "#/components/schemas/CancellationReason"
2202
+ },
2203
+ {
2204
+ "type": [
2205
+ "string",
2206
+ "null"
2207
+ ],
2208
+ "description": "Why the license was canceled. `null` when it wasn't."
2209
+ }
2210
+ ]
2211
+ },
2212
+ "subscription": {
2213
+ "$ref": "#/components/schemas/LicenseSubscription"
2214
+ },
2215
+ "cancellationComment": {
2216
+ "type": [
2217
+ "string",
2218
+ "null"
2219
+ ],
2220
+ "description": "Comment given when the license was canceled. `null` when there's none.",
2221
+ "example": "I only needed it for one project."
2222
+ },
2223
+ "stripeRefundId": {
2224
+ "type": [
2225
+ "string",
2226
+ "null"
2227
+ ],
2228
+ "description": "Id of the Stripe refund of the license's payment once it was canceled, through the Stripe integration. `null` otherwise.",
2229
+ "example": "re_3MtwBwLkdIwHu7ix0Bs5rA2b"
2230
+ },
1799
2231
  "updatedAt": {
1800
2232
  "type": "string",
1801
2233
  "format": "date-time",
@@ -1820,6 +2252,12 @@
1820
2252
  "maxVersion",
1821
2253
  "price",
1822
2254
  "status",
2255
+ "cancellableUntil",
2256
+ "canceledAt",
2257
+ "cancellationReason",
2258
+ "subscription",
2259
+ "cancellationComment",
2260
+ "stripeRefundId",
1823
2261
  "updatedAt"
1824
2262
  ]
1825
2263
  },
@@ -1872,27 +2310,102 @@
1872
2310
  "description": "Amount in major units of the currency: `49.99` for $49.99, `5000` for ¥5,000. `0` for a free license.",
1873
2311
  "example": 49.99
1874
2312
  },
1875
- "currency": {
2313
+ "currency": {
2314
+ "type": "string",
2315
+ "description": "ISO 4217 code of the currency, in upper case.",
2316
+ "example": "USD"
2317
+ }
2318
+ },
2319
+ "required": [
2320
+ "amount",
2321
+ "currency"
2322
+ ],
2323
+ "description": "Price paid for a license. Licenses have `null` instead when it's unknown, which leaves them out of the revenue stats."
2324
+ },
2325
+ "LicenseStatus": {
2326
+ "type": "string",
2327
+ "enum": [
2328
+ "active",
2329
+ "inactive",
2330
+ "expired",
2331
+ "canceled"
2332
+ ],
2333
+ "description": "`canceled` when the license was canceled, `expired` when it has expired, `active` when a machine validated its key in the last 90 days, `inactive` otherwise.",
2334
+ "example": "active"
2335
+ },
2336
+ "LicenseSubscription": {
2337
+ "type": [
2338
+ "object",
2339
+ "null"
2340
+ ],
2341
+ "properties": {
2342
+ "stripeSubscriptionId": {
2343
+ "type": "string",
2344
+ "example": "sub_1MowQVLkdIwHu7ix"
2345
+ },
2346
+ "status": {
2347
+ "type": "string",
2348
+ "description": "Status of the subscription in Stripe: `active`, `trialing`, `past_due` while a failed payment is retried, `canceled`, `unpaid`, `paused`, `incomplete` or `incomplete_expired`.",
2349
+ "example": "active"
2350
+ },
2351
+ "interval": {
2352
+ "type": "string",
2353
+ "enum": [
2354
+ "day",
2355
+ "week",
2356
+ "month",
2357
+ "year"
2358
+ ]
2359
+ },
2360
+ "intervalCount": {
2361
+ "type": "integer",
2362
+ "description": "Number of intervals between payments.",
2363
+ "example": 1
2364
+ },
2365
+ "quantity": {
2366
+ "type": "integer",
2367
+ "description": "Units subscribed, multiplying the policy's machines.",
2368
+ "example": 1
2369
+ },
2370
+ "price": {
2371
+ "allOf": [
2372
+ {
2373
+ "$ref": "#/components/schemas/Price"
2374
+ },
2375
+ {
2376
+ "description": "Price of one unit for one period, before taxes."
2377
+ }
2378
+ ]
2379
+ },
2380
+ "currentPeriodEnd": {
1876
2381
  "type": "string",
1877
- "description": "ISO 4217 code of the currency, in upper case.",
1878
- "example": "USD"
2382
+ "format": "date-time",
2383
+ "description": "End of the paid period, when the subscription renews or ends."
2384
+ },
2385
+ "cancelAtPeriodEnd": {
2386
+ "type": "boolean",
2387
+ "description": "Whether the subscription ends at the end of the period."
2388
+ },
2389
+ "endedAt": {
2390
+ "type": [
2391
+ "string",
2392
+ "null"
2393
+ ],
2394
+ "format": "date-time"
1879
2395
  }
1880
2396
  },
1881
2397
  "required": [
1882
- "amount",
1883
- "currency"
1884
- ],
1885
- "description": "Price paid for a license. Licenses have `null` instead when it's unknown, which leaves them out of the revenue stats."
1886
- },
1887
- "LicenseStatus": {
1888
- "type": "string",
1889
- "enum": [
1890
- "active",
1891
- "inactive",
1892
- "expired"
2398
+ "stripeSubscriptionId",
2399
+ "status",
2400
+ "interval",
2401
+ "intervalCount",
2402
+ "quantity",
2403
+ "price",
2404
+ "currentPeriodEnd",
2405
+ "cancelAtPeriodEnd",
2406
+ "endedAt"
1893
2407
  ],
1894
- "description": "`expired` when the license has expired, `active` when a machine validated its key in the last 90 days, `inactive` otherwise.",
1895
- "example": "active"
2408
+ "description": "The Stripe subscription keeping the license valid, `null` when it isn't a subscription license. The license expires when it stops being paid, includes every update, and can't be canceled during a money back guarantee."
1896
2409
  },
1897
2410
  "CreateLicense": {
1898
2411
  "type": "object",
@@ -1959,6 +2472,15 @@
1959
2472
  "maxVersion": {
1960
2473
  "$ref": "#/components/schemas/LicenseMaxVersionInput"
1961
2474
  },
2475
+ "cancellableUntil": {
2476
+ "type": [
2477
+ "string",
2478
+ "null"
2479
+ ],
2480
+ "format": "date-time",
2481
+ "description": "End of the money back guarantee: until then, the customer can cancel the license from the customer portal, or with a token. `null` to not let them. Defaults to the creation date plus the policy's `moneyBackGuaranteeDays`, or `null` when it has none.",
2482
+ "example": "2026-02-14T09:30:00.000Z"
2483
+ },
1962
2484
  "sendKey": {
1963
2485
  "type": "boolean",
1964
2486
  "description": "`true` emails the key to the customer once the license is created, like `sendLicenseKeys`. Sent in the background: a failed email doesn't fail the creation. In test products, it's only sent when the customer's email is a member's of the organization. Defaults to `false`.",
@@ -2182,9 +2704,40 @@
2182
2704
  },
2183
2705
  "price": {
2184
2706
  "$ref": "#/components/schemas/PriceInput"
2707
+ },
2708
+ "cancellableUntil": {
2709
+ "type": [
2710
+ "string",
2711
+ "null"
2712
+ ],
2713
+ "format": "date-time",
2714
+ "description": "End of the money back guarantee: until then, the customer can cancel the license from the customer portal, or with a token. `null` to not let them.",
2715
+ "example": "2026-02-14T09:30:00.000Z"
2185
2716
  }
2186
2717
  }
2187
2718
  },
2719
+ "CancelLicense": {
2720
+ "type": "object",
2721
+ "properties": {
2722
+ "reason": {
2723
+ "$ref": "#/components/schemas/CancellationReason"
2724
+ },
2725
+ "comment": {
2726
+ "type": "string",
2727
+ "maxLength": 2000,
2728
+ "description": "What the customer said about the cancellation.",
2729
+ "example": "I only needed it for one project."
2730
+ },
2731
+ "refund": {
2732
+ "type": "boolean",
2733
+ "description": "Whether to refund the license's Stripe payment, when it was bought through the Stripe integration. Defaults to the integration's `autoRefund`. Ignored for tokens, which always follow `autoRefund`, and when the integration's `canRefund` is false: its API key can't create refunds.",
2734
+ "example": true
2735
+ }
2736
+ },
2737
+ "required": [
2738
+ "reason"
2739
+ ]
2740
+ },
2188
2741
  "SendLicenseKeys": {
2189
2742
  "type": "object",
2190
2743
  "properties": {
@@ -2258,9 +2811,10 @@
2258
2811
  "type": "string",
2259
2812
  "enum": [
2260
2813
  "active",
2261
- "deactivated"
2814
+ "deactivated",
2815
+ "locked"
2262
2816
  ],
2263
- "description": "Whether the machine counts as using the license.",
2817
+ "description": "Whether the machine counts as using the license. A `locked` machine doesn't, and its validations are refused with `machine_locked`: it overrode a machine that came back. See `validateKey`.",
2264
2818
  "example": "active"
2265
2819
  },
2266
2820
  "version": {
@@ -2271,6 +2825,14 @@
2271
2825
  "description": "Version of the application last validated on the machine.",
2272
2826
  "example": "1.4.2"
2273
2827
  },
2828
+ "overriddenByMachineId": {
2829
+ "type": [
2830
+ "string",
2831
+ "null"
2832
+ ],
2833
+ "description": "For a machine deactivated or locked by automatic machine changes, the machine that took its place. Validating the key on a deactivated machine that has one takes its place back. `null` otherwise.",
2834
+ "example": null
2835
+ },
2274
2836
  "lastValidatedAt": {
2275
2837
  "type": [
2276
2838
  "string",
@@ -2299,6 +2861,7 @@
2299
2861
  "name",
2300
2862
  "status",
2301
2863
  "version",
2864
+ "overriddenByMachineId",
2302
2865
  "lastValidatedAt",
2303
2866
  "createdAt",
2304
2867
  "updatedAt"
@@ -2311,11 +2874,11 @@
2311
2874
  "type": "string",
2312
2875
  "format": "uri",
2313
2876
  "description": "Link to the customer portal, holding the token.",
2314
- "example": "https://app.example.com/portal/x7Kp2QaZ?token=eyJhbGciOi…"
2877
+ "example": "https://app.licensemanager.dev/portal/x7Kp2QaZ?token=eyJhbGciOi…"
2315
2878
  },
2316
2879
  "token": {
2317
2880
  "type": "string",
2318
- "description": "Customer token in the link, with the `customer:read`, `license:read` and `machine:write` permissions.",
2881
+ "description": "Customer token in the link, with the `customer:read`, `license:read`, `license:cancel` and `machine:write` permissions.",
2319
2882
  "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…"
2320
2883
  },
2321
2884
  "expiresAt": {
@@ -2346,6 +2909,30 @@
2346
2909
  "expiresInSeconds"
2347
2910
  ]
2348
2911
  },
2912
+ "BillingPortalSession": {
2913
+ "type": "object",
2914
+ "properties": {
2915
+ "url": {
2916
+ "type": "string",
2917
+ "description": "Link to the Stripe customer portal, to open right away: it expires shortly.",
2918
+ "example": "https://billing.stripe.com/p/session/test_YWNjdF8x"
2919
+ }
2920
+ },
2921
+ "required": [
2922
+ "url"
2923
+ ]
2924
+ },
2925
+ "CreateBillingPortalSession": {
2926
+ "type": "object",
2927
+ "properties": {
2928
+ "returnUrl": {
2929
+ "type": "string",
2930
+ "format": "uri",
2931
+ "description": "Where the Stripe customer portal links back to: a page of the customer portal, or of one of the product's `returnDomains`. The customer portal of the product by default.",
2932
+ "example": "https://example.com/account"
2933
+ }
2934
+ }
2935
+ },
2349
2936
  "SendPortalLink": {
2350
2937
  "type": "object",
2351
2938
  "properties": {
@@ -2516,8 +3103,10 @@
2516
3103
  "enum": [
2517
3104
  "customer:read",
2518
3105
  "customer:write",
3106
+ "customer:billing",
2519
3107
  "license:read",
2520
3108
  "license:write",
3109
+ "license:cancel",
2521
3110
  "machine:read",
2522
3111
  "machine:write"
2523
3112
  ]
@@ -2551,14 +3140,16 @@
2551
3140
  "enum": [
2552
3141
  "customer:read",
2553
3142
  "customer:write",
3143
+ "customer:billing",
2554
3144
  "license:read",
2555
3145
  "license:write",
3146
+ "license:cancel",
2556
3147
  "machine:read",
2557
3148
  "machine:write"
2558
3149
  ]
2559
3150
  },
2560
3151
  "minItems": 1,
2561
- "description": "Permissions of the token. A write permission implies the matching read one. A token creating a token can only give the permissions it has.",
3152
+ "description": "Permissions of the token. A write permission implies the other ones of its resource, like the read one: `license:write` implies `license:read` and `license:cancel`. A token creating a token can only give the permissions it has.",
2562
3153
  "example": [
2563
3154
  "license:read",
2564
3155
  "machine:write"
@@ -2669,9 +3260,15 @@
2669
3260
  "license.updated",
2670
3261
  "license.validated",
2671
3262
  "license.validation_failed",
3263
+ "license.canceled",
3264
+ "license.refunded",
3265
+ "license.refund_failed",
3266
+ "license.payment_succeeded",
3267
+ "license.payment_failed",
3268
+ "license.subscription_ended",
2672
3269
  "machine.deactivated"
2673
3270
  ],
2674
- "description": "- `policy.created`: `payload` holds the policy's settings.\n- `policy.updated`: `payload.changes` maps each changed field to its previous (`from`) and new (`to`) value. Making a policy the default also updates the previous default one.\n- `license.created`: `payload` holds the license's key, `policyId`, `customerId` and settings. Not linked to the policy.\n- `license.updated`: `payload.changes`, like `policy.updated`.\n- `license.validated`: a machine validated the license key. `payload` holds the machine's `fingerprint`, `name` and `version`, and whether the validation created the machine (`machineCreated`) or activated it again (`machineActivated`).\n- `license.validation_failed`: validating the license key failed. `payload` holds the error `code` and `message`, and the `fingerprint`, `name`, `platform` and `version` sent, the name and platform being the machine's when none is sent. Linked to the machine when it exists.\n- `machine.deactivated`: a machine was deactivated, so it no longer counts as using the license. `payload` holds the machine's `fingerprint` and `name`. Linked to the license and the machine.\n\nAn update that changes nothing records no event. Deletions record no events, and customers, products and releases have none.",
3271
+ "description": "- `policy.created`: `payload` holds the policy's settings.\n- `policy.updated`: `payload.changes` maps each changed field to its previous (`from`) and new (`to`) value. Making a policy the default also updates the previous default one.\n- `license.created`: `payload` holds the license's key, `policyId`, `customerId` and settings. Not linked to the policy.\n- `license.updated`: `payload.changes`, like `policy.updated`.\n- `license.validated`: a machine validated the license key. `payload` holds the machine's `fingerprint`, `name` and `version`, whether the validation created the machine (`machineCreated`) or activated it again (`machineActivated`), the `overriddenMachineId` of the machine it deactivated to take its place, `null` when none, and the `lockedMachineIds` of the machines it locked as it took its place back (see `validateKey`).\n- `license.validation_failed`: validating the license key failed. `payload` holds the error `code` and `message`, and the `fingerprint`, `name`, `platform` and `version` sent, the name and platform being the machine's when none is sent. Linked to the machine when it exists.\n- `license.canceled`: the license was canceled. `payload` holds the cancellation `reason` and `comment`, and whether its Stripe payment is being refunded (`refund`).\n- `license.refunded`: the Stripe payment of a canceled license was refunded. `payload` holds the Stripe `refundId` and the `amount` refunded. Has the `integration` actor.\n- `license.refund_failed`: refunding the Stripe payment of a canceled license failed. `payload` holds the error `message`: refund it from Stripe. Has the `integration` actor.\n- `license.payment_succeeded`: a payment of the license's Stripe subscription succeeded, its first one included. `payload` holds the Stripe `invoiceId`, the `amount` paid before taxes and its `currency`, Stripe's `billingReason` (`subscription_create`, `subscription_cycle`, `subscription_update`…) and the `periodStart` and `periodEnd` it pays for. Invoices with nothing to pay, like for a trial, record none. Has the `integration` actor.\n- `license.payment_failed`: a payment of the license's Stripe subscription failed. `payload` holds the Stripe `invoiceId`, the `amount` due and its `currency`, the `attemptCount` and when Stripe tries again (`nextAttemptAt`, `null` when it won't). The license stays valid for the integration's grace days after its period ends. Has the `integration` actor.\n- `license.subscription_ended`: the license's Stripe subscription ended, canceled or no longer paid, so the license expired. `payload` holds the subscription's `status` and Stripe's cancellation `reason`. Has the `integration` actor.\n- `machine.deactivated`: a machine was deactivated or locked, so it no longer counts as using the license. `payload` holds the machine's `fingerprint` and `name`, the `overriddenByMachineId` of the machine that took its place, `null` when it was deactivated through the API, and whether it was `locked`. Deactivations by validations have the `automation` actor. Linked to the license and the machine.\n\nAn update that changes nothing records no event. Deletions record no events, and customers, products and releases have none.",
2675
3272
  "example": "license.validated"
2676
3273
  },
2677
3274
  "EventMachine": {
@@ -2734,7 +3331,7 @@
2734
3331
  "string",
2735
3332
  "null"
2736
3333
  ],
2737
- "example": "jane@example.com"
3334
+ "example": "jane@licensemanager.dev"
2738
3335
  },
2739
3336
  "image": {
2740
3337
  "type": [
@@ -2890,7 +3487,7 @@
2890
3487
  "items": {
2891
3488
  "$ref": "#/components/schemas/StripePermission"
2892
3489
  },
2893
- "description": "Permissions the restricted API key needs, and why. Every other permission can stay `None`."
3490
+ "description": "Permissions the restricted API key needs, and why, the `optional` ones included. Every other permission can stay `None`."
2894
3491
  },
2895
3492
  "integration": {
2896
3493
  "anyOf": [
@@ -2936,12 +3533,24 @@
2936
3533
  "reason": {
2937
3534
  "type": "string",
2938
3535
  "example": "Lists your prices so you can choose which ones create licenses, and checks they can still be paid."
3536
+ },
3537
+ "optional": {
3538
+ "type": "boolean",
3539
+ "description": "Whether the key can connect without it, losing what it's for, explained by `reason`.",
3540
+ "example": false
3541
+ },
3542
+ "subscriptions": {
3543
+ "type": "boolean",
3544
+ "description": "Whether it's only needed to sell subscriptions: checked once a recurring price is mapped.",
3545
+ "example": false
2939
3546
  }
2940
3547
  },
2941
3548
  "required": [
2942
3549
  "resource",
2943
3550
  "access",
2944
- "reason"
3551
+ "reason",
3552
+ "optional",
3553
+ "subscriptions"
2945
3554
  ]
2946
3555
  },
2947
3556
  "Integration": {
@@ -2977,7 +3586,7 @@
2977
3586
  "webhookUrl": {
2978
3587
  "type": "string",
2979
3588
  "description": "URL of the webhook endpoint created on the Stripe account.",
2980
- "example": "https://api.example.com/v1/webhooks/stripe/i8Fz4QnRt"
3589
+ "example": "https://api.licensemanager.dev/v1/webhooks/stripe/i8Fz4QnRt"
2981
3590
  },
2982
3591
  "status": {
2983
3592
  "$ref": "#/components/schemas/IntegrationStatus"
@@ -3006,6 +3615,31 @@
3006
3615
  "format": "date-time",
3007
3616
  "description": "Last time it received an event from the provider."
3008
3617
  },
3618
+ "autoRefund": {
3619
+ "type": "boolean",
3620
+ "description": "Whether canceling a license bought through Stripe refunds its payment. On by default, always off when `canRefund` is false.",
3621
+ "example": true
3622
+ },
3623
+ "canRefund": {
3624
+ "type": "boolean",
3625
+ "description": "Whether the API key has the optional Refunds: Write permission, checked when connecting and every day. Without it, canceled licenses aren't refunded: refund their payments from Stripe.",
3626
+ "example": true
3627
+ },
3628
+ "canManageSubscriptions": {
3629
+ "type": "boolean",
3630
+ "description": "Whether the API key has the optional Subscriptions: Write permission, checked when connecting and every day. Without it, subscription licenses can't be canceled from the app: cancel their subscriptions from Stripe.",
3631
+ "example": true
3632
+ },
3633
+ "canBillingPortal": {
3634
+ "type": "boolean",
3635
+ "description": "Whether the API key has the optional Customer portal: Write permission, checked when connecting and every day. Without it, the license portal doesn't link to the Stripe customer portal.",
3636
+ "example": true
3637
+ },
3638
+ "subscriptionGraceDays": {
3639
+ "type": "integer",
3640
+ "description": "Days a subscription license stays valid after its period ends while its renewal is unpaid, as Stripe retries the payment. 7 by default.",
3641
+ "example": 7
3642
+ },
3009
3643
  "createdAt": {
3010
3644
  "type": "string",
3011
3645
  "format": "date-time"
@@ -3022,6 +3656,11 @@
3022
3656
  "statusReason",
3023
3657
  "lastCheckedAt",
3024
3658
  "lastEventAt",
3659
+ "autoRefund",
3660
+ "canRefund",
3661
+ "canManageSubscriptions",
3662
+ "canBillingPortal",
3663
+ "subscriptionGraceDays",
3025
3664
  "createdAt"
3026
3665
  ]
3027
3666
  },
@@ -3069,6 +3708,32 @@
3069
3708
  "price": {
3070
3709
  "$ref": "#/components/schemas/Price"
3071
3710
  },
3711
+ "recurring": {
3712
+ "type": [
3713
+ "object",
3714
+ "null"
3715
+ ],
3716
+ "properties": {
3717
+ "interval": {
3718
+ "type": "string",
3719
+ "enum": [
3720
+ "day",
3721
+ "week",
3722
+ "month",
3723
+ "year"
3724
+ ]
3725
+ },
3726
+ "intervalCount": {
3727
+ "type": "integer",
3728
+ "example": 1
3729
+ }
3730
+ },
3731
+ "required": [
3732
+ "interval",
3733
+ "intervalCount"
3734
+ ],
3735
+ "description": "Billing period of a recurring price, `null` for a one-time price. Subscribing to a recurring price creates one subscription license, valid while it's paid."
3736
+ },
3072
3737
  "status": {
3073
3738
  "type": "string",
3074
3739
  "enum": [
@@ -3098,6 +3763,7 @@
3098
3763
  "stripePriceId",
3099
3764
  "productName",
3100
3765
  "price",
3766
+ "recurring",
3101
3767
  "status",
3102
3768
  "statusReason",
3103
3769
  "createdAt"
@@ -3109,7 +3775,7 @@
3109
3775
  "apiKey": {
3110
3776
  "type": "string",
3111
3777
  "minLength": 1,
3112
- "description": "Restricted API key of the Stripe account (`rk_live_…` for live products, `rk_test_…` for test products), with the `requiredPermissions` of `getStripeIntegration`. A secret key works too, but gives the app more access than it needs.",
3778
+ "description": "Restricted API key of the Stripe account (`rk_live_…` for live products, `rk_test_…` for test products), with the `requiredPermissions` of `getStripeIntegration`. Without the optional ones, it connects with what they're for turned off. A secret key works too, but gives the app more access than it needs.",
3113
3779
  "example": "rk_live_51Nv0FG…"
3114
3780
  }
3115
3781
  },
@@ -3117,6 +3783,23 @@
3117
3783
  "apiKey"
3118
3784
  ]
3119
3785
  },
3786
+ "UpdateIntegration": {
3787
+ "type": "object",
3788
+ "properties": {
3789
+ "autoRefund": {
3790
+ "type": "boolean",
3791
+ "description": "Whether canceling a license bought through Stripe refunds its payment. Can only be turned on when the integration's `canRefund` is true: the API key has the Refunds: Write permission.",
3792
+ "example": false
3793
+ },
3794
+ "subscriptionGraceDays": {
3795
+ "type": "integer",
3796
+ "minimum": 0,
3797
+ "maximum": 60,
3798
+ "description": "Days a subscription license stays valid after its period ends while its renewal is unpaid, as Stripe retries the payment.",
3799
+ "example": 7
3800
+ }
3801
+ }
3802
+ },
3120
3803
  "StripePrice": {
3121
3804
  "type": "object",
3122
3805
  "properties": {
@@ -3142,6 +3825,32 @@
3142
3825
  "price": {
3143
3826
  "$ref": "#/components/schemas/Price"
3144
3827
  },
3828
+ "recurring": {
3829
+ "type": [
3830
+ "object",
3831
+ "null"
3832
+ ],
3833
+ "properties": {
3834
+ "interval": {
3835
+ "type": "string",
3836
+ "enum": [
3837
+ "day",
3838
+ "week",
3839
+ "month",
3840
+ "year"
3841
+ ]
3842
+ },
3843
+ "intervalCount": {
3844
+ "type": "integer",
3845
+ "example": 1
3846
+ }
3847
+ },
3848
+ "required": [
3849
+ "interval",
3850
+ "intervalCount"
3851
+ ],
3852
+ "description": "Billing period of a recurring price, `null` for a one-time price."
3853
+ },
3145
3854
  "mapped": {
3146
3855
  "type": "boolean",
3147
3856
  "description": "Whether the price already creates licenses of a product."
@@ -3153,6 +3862,7 @@
3153
3862
  "productName",
3154
3863
  "nickname",
3155
3864
  "price",
3865
+ "recurring",
3156
3866
  "mapped"
3157
3867
  ]
3158
3868
  },
@@ -3162,7 +3872,7 @@
3162
3872
  "stripePriceId": {
3163
3873
  "type": "string",
3164
3874
  "minLength": 1,
3165
- "description": "Active one-time price of the Stripe account.",
3875
+ "description": "Active one-time or licensed recurring price of the Stripe account.",
3166
3876
  "example": "price_1MoBy5LkdIwHu7ix"
3167
3877
  },
3168
3878
  "policyId": {
@@ -3200,7 +3910,7 @@
3200
3910
  ],
3201
3911
  "operationId": "validateKey",
3202
3912
  "summary": "Validate a license key",
3203
- "description": "Checks the license key of the product on a machine and returns a token the application can verify offline with the organization's public key. Doesn't need authentication.\n\nThe license must allow the `version`: up to its `maxVersion.version` when it has one, like `\"2.3\"` for every 2.3.z version and older. Its `maxVersion.lockMajorAt` and `maxVersion.lockMinorAt` are enforced once they hard-code `maxVersion.version`, within the hour they're reached. When it doesn't, the error's `latestAllowedVersion` is the latest release of the product the license allows, so the application can offer to install it.\n\nThe machine with the `fingerprint` is created when the license has none, and activated when it's deactivated, unless the license is already active on its maximum number of machines. A machine that's already active is never refused for the maximum number of machines, even when it was lowered below the number of active machines. Its `version`, and its `name` and `platform` when given, are updated: they can't be cleared.\n\nThe checks run in this order, and the first one failing answers: the license has expired, then it doesn't allow the version, then it's on its maximum number of machines. Each failure is recorded as a `license.validation_failed` event, except for keys that don't exist.\n\nChanges to the license, like deactivating the machine or shortening its expiration, don't revoke the tokens already returned: the application only sees them when it validates its key again, at the latest when its token expires.",
3913
+ "description": "Checks the license key of the product on a machine and returns a token the application can verify offline with the organization's public key. Doesn't need authentication.\n\nThe license must allow the `version`: up to its `maxVersion.version` when it has one, like `\"2.3\"` for every 2.3.z version and older. Its `maxVersion.lockMajorAt` and `maxVersion.lockMinorAt` are enforced once they hard-code `maxVersion.version`, within the hour they're reached. When it doesn't, the error's `latestAllowedVersion` is the latest release of the product the license allows, so the application can offer to install it.\n\nThe machine with the `fingerprint` is created when the license has none, and activated when it's deactivated, unless the license is already active on its maximum number of machines. A machine that's already active is never refused for the maximum number of machines, even when it was lowered below the number of active machines. Its `version`, and its `name` and `platform` when given, are updated: they can't be cleared.\n\nUnless the product disables `autoMachineChange`, a new machine isn't refused when the license is on its maximum number of machines: it takes the place of the active machine validated the longest ago, which is deactivated with its `overriddenByMachineId` set to the new machine, and recorded as a `machine.deactivated` event. When a machine deactivated this way is validated again, it takes its place back: the machine that overrode it, and the ones that overrode that one in turn, are locked, with their `overriddenByMachineId` set to the machine coming back. A locked machine is always refused, so two machines can't take turns on one place.\n\nThe checks run in this order, and the first one failing answers: the license has expired or was canceled, then it doesn't allow the version, then the machine is locked, then it's on its maximum number of machines. Each failure is recorded as a `license.validation_failed` event, except for keys that don't exist.\n\nChanges to the license, like deactivating the machine or shortening its expiration, don't revoke the tokens already returned: the application only sees them when it validates its key again, at the latest when its token expires.",
3204
3914
  "security": [],
3205
3915
  "parameters": [
3206
3916
  {
@@ -3245,7 +3955,7 @@
3245
3955
  }
3246
3956
  },
3247
3957
  "403": {
3248
- "description": "`license_expired`: the license has expired. `version_not_allowed`: the license doesn't allow the version, with the `latestAllowedVersion`. `machine_limit_reached`: the license is already active on its maximum number of machines",
3958
+ "description": "`license_expired`: the license has expired, or was canceled. `version_not_allowed`: the license doesn't allow the version, with the `latestAllowedVersion`. `machine_locked`: the machine was locked after the machine it overrode came back. `machine_limit_reached`: the license is already active on its maximum number of machines, and the product disables `autoMachineChange` or the machine isn't new",
3249
3959
  "content": {
3250
3960
  "application/json": {
3251
3961
  "schema": {
@@ -3555,7 +4265,7 @@
3555
4265
  ],
3556
4266
  "operationId": "createProduct",
3557
4267
  "summary": "Create a product",
3558
- "description": "Without a `slug`, the product gets one derived from its name: lower cased, without accents and special characters (`Élan Pro 2!` becomes `elan-pro-2`).\nWhen another product of the organization already uses it, a dash and 3 random lowercase letters or digits are appended (`elan-pro-2-k3f`), until the slug is free.\n\nAn explicit `slug` is used as is: the request fails with a `409` when it's taken, it's never changed.\n\nSlug uniqueness is enforced by the database, so when concurrent requests end up with the same slug, only one succeeds and the others get a `409`, even without an explicit `slug`. Retrying such a request without a `slug` derives a new one.\n\nThe product is created with its default policy, named `Default policy` with the `default` slug, whose settings are given by `defaultPolicy`. Omitted settings default to licenses that never expire, on a single machine, offline for one week, for every version of the product. Unlike `createPolicy`, where `maxMachines` defaults to no limit.\n\nWith `isTestFromProductId`, the product is a test product: its id, its slug, and the ids, slugs and license keys of its resources start with `test_` (`TEST-` for license keys), and only test API keys reach it. It starts with copies of the policies, releases and portal settings of the live product, `defaultPolicy` is ignored. Later changes to the live product aren't copied: reset the test product to copy them again. Test API keys can only create test products, live API keys only live ones.\n\n**Required role permission**: `product:create` when called with a session, granted by the member's role. API keys aren't limited by roles.",
4268
+ "description": "Without a `slug`, the product gets one derived from its name: lower cased, without accents and special characters (`Élan Pro 2!` becomes `elan-pro-2`).\nWhen another product of the organization already uses it, a dash and 3 random lowercase letters or digits are appended (`elan-pro-2-k3f`), until the slug is free.\n\nAn explicit `slug` is used as is: the request fails with a `409` when it's taken, it's never changed.\n\nSlug uniqueness is enforced by the database, so when concurrent requests end up with the same slug, only one succeeds and the others get a `409`, even without an explicit `slug`. Retrying such a request without a `slug` derives a new one.\n\nThe product is created with its default policy, named `Default policy` with the `default` slug, whose settings are given by `defaultPolicy`. Omitted settings default to licenses that never expire, on a single machine, offline for one week, for every version of the product. Unlike `createPolicy`, where `maxMachines` defaults to no limit.\n\nWith `isTestFromProductId`, the product is a test product: its id, its slug, and the ids, slugs and license keys of its resources start with `test_` (`TEST-` for license keys), and only test API keys reach it. It starts with copies of the policies, releases, portal settings and `autoMachineChange` of the live product, `defaultPolicy` is ignored. Later changes to the live product aren't copied: reset the test product to copy them again. Test API keys can only create test products, live API keys only live ones.\n\n**Required role permission**: `product:create` when called with a session, granted by the member's role. API keys aren't limited by roles.",
3559
4269
  "x-required-permissions": [
3560
4270
  "product:create"
3561
4271
  ],
@@ -3963,7 +4673,7 @@
3963
4673
  ],
3964
4674
  "operationId": "resetProduct",
3965
4675
  "summary": "Reset a test product",
3966
- "description": "Deletes the customers, licenses, machines, policies and releases of the test product, with their events, then copies the policies, releases and portal settings of its live product again. Its id, name and slug are kept, but the copied policies get new ids. The tokens of its customers, licenses and machines stop working.\n\n**Required role permission**: `product:update` when called with a session, granted by the member's role. API keys aren't limited by roles.",
4676
+ "description": "Deletes the customers, licenses, machines, policies and releases of the test product, with their events, then copies the policies, releases, portal settings and `autoMachineChange` of its live product again. Its id, name and slug are kept, but the copied policies get new ids. The tokens of its customers, licenses and machines stop working.\n\n**Required role permission**: `product:update` when called with a session, granted by the member's role. API keys aren't limited by roles.",
3967
4677
  "x-required-permissions": [
3968
4678
  "product:update"
3969
4679
  ],
@@ -6001,7 +6711,7 @@
6001
6711
  ],
6002
6712
  "operationId": "createLicense",
6003
6713
  "summary": "Create a license",
6004
- "description": "Without a `key`, the license gets a random one, like `7KQ2M-X9ZPA-H4TRC-WN6EY-B3JDF`: 5 groups of 5 characters separated by dashes, each character an upper case letter or a digit other than `O`, `0`, `I` and `1`. That's 32 possible characters encoding 5 bits each, for 125 random bits.\n\nWithout a `policyId`, the license follows the product's default policy.\n\nWithout an `expiresAt`, the license expires after the duration of its policy, or never when the policy has none. `expiresAt: null` makes it never expire.\n\nWithout a `price`, the price paid for the license is unknown: it's left out of the revenue stats.\n\nThe values of `maxVersion` left out are taken from the max version of the policy: its hard-coded version, or the times after the creation of the license its versions lock.\n\nThe license belongs to the product's customer with the given `email`, which is created when there's none.\n\nWith `sendKey: true`, the key is emailed to the customer once the license is created.\n\nThe license copies the settings of its policy when it's created: later changes to the policy don't affect it. Its key, policy and customer can't be changed afterwards.\n\n**Required role permission**: `license:create` when called with a session, granted by the member's role. API keys aren't limited by roles.",
6714
+ "description": "Without a `key`, the license gets a random one, like `7KQ2M-X9ZPA-H4TRC-WN6EY-B3JDF`: 5 groups of 5 characters separated by dashes, each character an upper case letter or a digit other than `O`, `0`, `I` and `1`. That's 32 possible characters encoding 5 bits each, for 125 random bits.\n\nWithout a `policyId`, the license follows the product's default policy.\n\nWithout an `expiresAt`, the license expires after the duration of its policy, or never when the policy has none. `expiresAt: null` makes it never expire.\n\nWithout a `price`, the price paid for the license is unknown: it's left out of the revenue stats.\n\nWithout a `cancellableUntil`, the customer can cancel the license during the policy's `moneyBackGuaranteeDays` after its creation, or never when the policy has none.\n\nThe values of `maxVersion` left out are taken from the max version of the policy: its hard-coded version, or the times after the creation of the license its versions lock.\n\nThe license belongs to the product's customer with the given `email`, which is created when there's none.\n\nWith `sendKey: true`, the key is emailed to the customer once the license is created.\n\nThe license copies the settings of its policy when it's created: later changes to the policy don't affect it. Its key, policy and customer can't be changed afterwards.\n\n**Required role permission**: `license:create` when called with a session, granted by the member's role. API keys aren't limited by roles.",
6005
6715
  "x-required-permissions": [
6006
6716
  "license:create"
6007
6717
  ],
@@ -6605,7 +7315,7 @@
6605
7315
  ],
6606
7316
  "operationId": "updateLicense",
6607
7317
  "summary": "Update a license",
6608
- "description": "The key, policy and customer of a license can't be changed: sending them does nothing.\n\nThe changes apply to the next validations of the key, not to the license tokens `validateKey` already returned, which stay valid until they expire. Lowering `maxMachines` doesn't deactivate machines: the ones already active keep validating, and new ones are refused until enough are deactivated.\n\n**Required role permission**: `license:update` when called with a session, granted by the member's role. API keys aren't limited by roles.",
7318
+ "description": "The key, policy and customer of a license can't be changed: sending them does nothing.\n\nThe changes apply to the next validations of the key, not to the license tokens `validateKey` already returned, which stay valid until they expire. Lowering `maxMachines` doesn't deactivate machines: the ones already active keep validating, and new ones replace them one at a time when the product has `autoMachineChange`, or are refused until enough are deactivated.\n\n**Required role permission**: `license:update` when called with a session, granted by the member's role. API keys aren't limited by roles.",
6609
7319
  "x-required-permissions": [
6610
7320
  "license:update"
6611
7321
  ],
@@ -6681,6 +7391,16 @@
6681
7391
  }
6682
7392
  }
6683
7393
  },
7394
+ "409": {
7395
+ "description": "`subscription_managed`: the license is a subscription license, whose `expiresAt`, `price`, `maxVersion` and `cancellableUntil` follow its Stripe subscription",
7396
+ "content": {
7397
+ "application/json": {
7398
+ "schema": {
7399
+ "$ref": "#/components/schemas/Error"
7400
+ }
7401
+ }
7402
+ }
7403
+ },
6684
7404
  "422": {
6685
7405
  "description": "`validation_error`: invalid path, query or body, with the `issues`",
6686
7406
  "content": {
@@ -6798,17 +7518,123 @@
6798
7518
  }
6799
7519
  }
6800
7520
  }
6801
- }
6802
- }
6803
- },
6804
- "/v1/licenses/{id}/stats": {
6805
- "get": {
6806
- "tags": [
6807
- "Licenses"
7521
+ }
7522
+ }
7523
+ },
7524
+ "/v1/licenses/{id}/stats": {
7525
+ "get": {
7526
+ "tags": [
7527
+ "Licenses"
7528
+ ],
7529
+ "operationId": "getLicenseStats",
7530
+ "summary": "Get the stats of a license",
7531
+ "description": "Daily number of successful activations of the license over the last 90 days, today included, per machine and per version of the application. Days are in UTC.",
7532
+ "parameters": [
7533
+ {
7534
+ "schema": {
7535
+ "type": "string",
7536
+ "example": "l3Nv8KwB"
7537
+ },
7538
+ "required": true,
7539
+ "name": "id",
7540
+ "in": "path"
7541
+ }
7542
+ ],
7543
+ "responses": {
7544
+ "200": {
7545
+ "description": "Stats of the license",
7546
+ "content": {
7547
+ "application/json": {
7548
+ "schema": {
7549
+ "$ref": "#/components/schemas/LicenseStats"
7550
+ }
7551
+ }
7552
+ }
7553
+ },
7554
+ "401": {
7555
+ "description": "`unauthorized`: no credentials. `invalid_api_key`: the API key doesn't exist, was revoked or has expired. `invalid_token`: the token's signature is invalid, it has expired, or the resource it's scoped to was deleted",
7556
+ "content": {
7557
+ "application/json": {
7558
+ "schema": {
7559
+ "$ref": "#/components/schemas/Error"
7560
+ }
7561
+ }
7562
+ }
7563
+ },
7564
+ "402": {
7565
+ "description": "`plan_limit_exceeded`: the organization stayed over its plan's limits after the grace period, its API is blocked until it upgrades. `plan_limit_reached`: creating the resource would go over the plan's limit",
7566
+ "content": {
7567
+ "application/json": {
7568
+ "schema": {
7569
+ "$ref": "#/components/schemas/Error"
7570
+ }
7571
+ }
7572
+ }
7573
+ },
7574
+ "403": {
7575
+ "description": "`token_not_allowed`: the endpoint doesn't accept tokens, or not of this type. `insufficient_scope`: the token lacks the permission. `no_active_organization` or `forbidden`: the session has no organization, or isn't a member of the one requested",
7576
+ "content": {
7577
+ "application/json": {
7578
+ "schema": {
7579
+ "$ref": "#/components/schemas/Error"
7580
+ }
7581
+ }
7582
+ }
7583
+ },
7584
+ "404": {
7585
+ "description": "License not found",
7586
+ "content": {
7587
+ "application/json": {
7588
+ "schema": {
7589
+ "$ref": "#/components/schemas/Error"
7590
+ }
7591
+ }
7592
+ }
7593
+ },
7594
+ "422": {
7595
+ "description": "`validation_error`: invalid path, query or body, with the `issues`",
7596
+ "content": {
7597
+ "application/json": {
7598
+ "schema": {
7599
+ "$ref": "#/components/schemas/Error"
7600
+ }
7601
+ }
7602
+ }
7603
+ },
7604
+ "429": {
7605
+ "description": "`rate_limited`: the API key made more than 10 requests in the last second. Wait for the `Retry-After` seconds",
7606
+ "content": {
7607
+ "application/json": {
7608
+ "schema": {
7609
+ "$ref": "#/components/schemas/Error"
7610
+ }
7611
+ }
7612
+ }
7613
+ }
7614
+ }
7615
+ }
7616
+ },
7617
+ "/v1/licenses/{id}/cancel": {
7618
+ "post": {
7619
+ "tags": [
7620
+ "Licenses"
7621
+ ],
7622
+ "operationId": "cancelLicense",
7623
+ "summary": "Cancel a license",
7624
+ "description": "Cancels the license right away: validating its key then fails with `license_expired`, like an expired license, and its status is `canceled`. A cancellation can't be undone.\n\nCustomers can cancel their licenses until their `cancellableUntil`, the end of their money back guarantee: tokens can't cancel a license past it, or without one. API keys and sessions can cancel any license.\n\nWhen the license was bought through the Stripe integration, its payment is refunded in the background, unless the integration disables `autoRefund`, its API key lacks the optional Refunds: Write permission (`canRefund`), or `refund` is `false`: the refund is its price paid, taxes included, and its id is set as `stripeRefundId`. The customer is emailed a confirmation, and the owners and admins of the organization are told about the cancellation and its refund. In test products, the customer is only emailed when their email is a member's of the organization.\n\nA subscription license isn't refunded: its Stripe subscription is canceled at once, without proration, which needs the integration's `canManageSubscriptions`.\n\nRecords a `license.canceled` event, then a `license.refunded` or `license.refund_failed` one once the refund is done.\n\n**Token access**: customer, license tokens with the `license:cancel` permission (or the matching write one). A token reaches the license it's scoped to, or the licenses of its customer.\n\n**Required role permission**: `license:cancel` when called with a session, granted by the member's role. API keys aren't limited by roles.",
7625
+ "security": [
7626
+ {
7627
+ "apiKey": []
7628
+ },
7629
+ {
7630
+ "token": [
7631
+ "license:cancel"
7632
+ ]
7633
+ }
7634
+ ],
7635
+ "x-required-permissions": [
7636
+ "license:cancel"
6808
7637
  ],
6809
- "operationId": "getLicenseStats",
6810
- "summary": "Get the stats of a license",
6811
- "description": "Daily number of successful activations of the license over the last 90 days, today included, per machine and per version of the application. Days are in UTC.",
6812
7638
  "parameters": [
6813
7639
  {
6814
7640
  "schema": {
@@ -6820,13 +7646,23 @@
6820
7646
  "in": "path"
6821
7647
  }
6822
7648
  ],
7649
+ "requestBody": {
7650
+ "required": true,
7651
+ "content": {
7652
+ "application/json": {
7653
+ "schema": {
7654
+ "$ref": "#/components/schemas/CancelLicense"
7655
+ }
7656
+ }
7657
+ }
7658
+ },
6823
7659
  "responses": {
6824
7660
  "200": {
6825
- "description": "Stats of the license",
7661
+ "description": "Canceled license",
6826
7662
  "content": {
6827
7663
  "application/json": {
6828
7664
  "schema": {
6829
- "$ref": "#/components/schemas/LicenseStats"
7665
+ "$ref": "#/components/schemas/License"
6830
7666
  }
6831
7667
  }
6832
7668
  }
@@ -6871,6 +7707,16 @@
6871
7707
  }
6872
7708
  }
6873
7709
  },
7710
+ "409": {
7711
+ "description": "`already_canceled`: the license is already canceled. `not_cancellable`: for tokens, the license has no money back guarantee, or it has ended, as subscription licenses have none. `subscription_managed`: the license is a subscription license, and the Stripe API key lacks the Subscriptions: Write permission to cancel its subscription",
7712
+ "content": {
7713
+ "application/json": {
7714
+ "schema": {
7715
+ "$ref": "#/components/schemas/Error"
7716
+ }
7717
+ }
7718
+ }
7719
+ },
6874
7720
  "422": {
6875
7721
  "description": "`validation_error`: invalid path, query or body, with the `issues`",
6876
7722
  "content": {
@@ -7098,7 +7944,8 @@
7098
7944
  "type": "string",
7099
7945
  "enum": [
7100
7946
  "active",
7101
- "deactivated"
7947
+ "deactivated",
7948
+ "locked"
7102
7949
  ],
7103
7950
  "description": "Only returns the machines with this status.",
7104
7951
  "example": "active"
@@ -7293,7 +8140,7 @@
7293
8140
  ],
7294
8141
  "operationId": "deactivateMachine",
7295
8142
  "summary": "Deactivate a machine",
7296
- "description": "The machine no longer counts as using the license, freeing its place when the license has a maximum number of machines. Validating the license key on the machine activates it again, unless the license is on its maximum number of machines by then. Deactivating a deactivated machine does nothing.\n\nThe license token `validateKey` returned to the machine isn't revoked: the application can keep running offline until it expires. Machines can't be deleted: they're deleted with their license.\n\n**Token access**: customer, license, machine tokens with the `machine:write` permission (or the matching write one). A token reaches the machine it's scoped to, or the machines of its license or customer.\n\n**Required role permission**: `machine:deactivate` when called with a session, granted by the member's role. API keys aren't limited by roles.",
8143
+ "description": "The machine no longer counts as using the license, freeing its place when the license has a maximum number of machines. Validating the license key on the machine activates it again, unless the license is on its maximum number of machines by then. Deactivating a deactivated or locked machine does nothing.\n\nThe license token `validateKey` returned to the machine isn't revoked: the application can keep running offline until it expires. Machines can't be deleted: they're deleted with their license.\n\n**Token access**: customer, license, machine tokens with the `machine:write` permission (or the matching write one). A token reaches the machine it's scoped to, or the machines of its license or customer.\n\n**Required role permission**: `machine:deactivate` when called with a session, granted by the member's role. API keys aren't limited by roles.",
7297
8144
  "security": [
7298
8145
  {
7299
8146
  "apiKey": []
@@ -7399,7 +8246,7 @@
7399
8246
  ],
7400
8247
  "operationId": "createPortalLink",
7401
8248
  "summary": "Create a link to the customer portal",
7402
- "description": "Link letting the customer see their licenses and deactivate their machines in the customer portal, without signing in. Anyone with the link can, until it expires or the customer is deleted: it can't be revoked otherwise. The token also works with the API endpoints that accept customer tokens.\n\nLinks can be created while the product's customer portal is disabled, but the portal answers `404` until it's enabled, and `sendPortalLink` refuses to email them.\n\n**Required role permission**: `portalLink:create` when called with a session, granted by the member's role. API keys aren't limited by roles.",
8249
+ "description": "Link letting the customer see their licenses, cancel them during their money back guarantee, and deactivate their machines in the customer portal, without signing in. Anyone with the link can, until it expires or the customer is deleted: it can't be revoked otherwise. The token also works with the API endpoints that accept customer tokens.\n\nLinks can be created while the product's customer portal is disabled, but the portal answers `404` until it's enabled, and `sendPortalLink` refuses to email them.\n\n**Required role permission**: `portalLink:create` when called with a session, granted by the member's role. API keys aren't limited by roles.",
7403
8250
  "x-required-permissions": [
7404
8251
  "portalLink:create"
7405
8252
  ],
@@ -7498,6 +8345,142 @@
7498
8345
  }
7499
8346
  }
7500
8347
  },
8348
+ "/v1/customers/{id}/billing-portal-sessions": {
8349
+ "post": {
8350
+ "tags": [
8351
+ "Portal"
8352
+ ],
8353
+ "operationId": "createBillingPortalSession",
8354
+ "summary": "Open the Stripe customer portal of a customer",
8355
+ "description": "Link to the Stripe customer portal of the customer's Stripe customer, where they manage their subscriptions: change their plan or payment method, see their invoices, cancel. Needs the Stripe integration's `canBillingPortal`, and the customer portal configured in the Stripe account's settings. The changes come back through the integration's webhook, keeping the subscription licenses in line.\n\n**Token access**: customer tokens with the `customer:billing` permission (or the matching write one). A customer token reaches its own customer.\n\n**Required role permission**: `portalLink:create` when called with a session, granted by the member's role. API keys aren't limited by roles.",
8356
+ "security": [
8357
+ {
8358
+ "apiKey": []
8359
+ },
8360
+ {
8361
+ "token": [
8362
+ "customer:billing"
8363
+ ]
8364
+ }
8365
+ ],
8366
+ "x-required-permissions": [
8367
+ "portalLink:create"
8368
+ ],
8369
+ "parameters": [
8370
+ {
8371
+ "schema": {
8372
+ "type": "string",
8373
+ "example": "c9Wd3RtE"
8374
+ },
8375
+ "required": true,
8376
+ "name": "id",
8377
+ "in": "path"
8378
+ }
8379
+ ],
8380
+ "requestBody": {
8381
+ "required": false,
8382
+ "content": {
8383
+ "application/json": {
8384
+ "schema": {
8385
+ "$ref": "#/components/schemas/CreateBillingPortalSession"
8386
+ }
8387
+ }
8388
+ }
8389
+ },
8390
+ "responses": {
8391
+ "201": {
8392
+ "description": "Created session",
8393
+ "content": {
8394
+ "application/json": {
8395
+ "schema": {
8396
+ "$ref": "#/components/schemas/BillingPortalSession"
8397
+ }
8398
+ }
8399
+ }
8400
+ },
8401
+ "401": {
8402
+ "description": "`unauthorized`: no credentials. `invalid_api_key`: the API key doesn't exist, was revoked or has expired. `invalid_token`: the token's signature is invalid, it has expired, or the resource it's scoped to was deleted",
8403
+ "content": {
8404
+ "application/json": {
8405
+ "schema": {
8406
+ "$ref": "#/components/schemas/Error"
8407
+ }
8408
+ }
8409
+ }
8410
+ },
8411
+ "402": {
8412
+ "description": "`plan_limit_exceeded`: the organization stayed over its plan's limits after the grace period, its API is blocked until it upgrades. `plan_limit_reached`: creating the resource would go over the plan's limit",
8413
+ "content": {
8414
+ "application/json": {
8415
+ "schema": {
8416
+ "$ref": "#/components/schemas/Error"
8417
+ }
8418
+ }
8419
+ }
8420
+ },
8421
+ "403": {
8422
+ "description": "`token_not_allowed`: the endpoint doesn't accept tokens, or not of this type. `insufficient_scope`: the token lacks the permission. `no_active_organization` or `forbidden`: the session has no organization, or isn't a member of the one requested",
8423
+ "content": {
8424
+ "application/json": {
8425
+ "schema": {
8426
+ "$ref": "#/components/schemas/Error"
8427
+ }
8428
+ }
8429
+ }
8430
+ },
8431
+ "404": {
8432
+ "description": "Customer not found",
8433
+ "content": {
8434
+ "application/json": {
8435
+ "schema": {
8436
+ "$ref": "#/components/schemas/Error"
8437
+ }
8438
+ }
8439
+ }
8440
+ },
8441
+ "409": {
8442
+ "description": "`no_stripe_customer`: the customer didn't pay through the Stripe integration. `billing_portal_unavailable`: the Stripe integration isn't connected, or its API key lacks the Customer portal: Write permission",
8443
+ "content": {
8444
+ "application/json": {
8445
+ "schema": {
8446
+ "$ref": "#/components/schemas/Error"
8447
+ }
8448
+ }
8449
+ }
8450
+ },
8451
+ "422": {
8452
+ "description": "`validation_error`: invalid path, query or body, with the `issues`",
8453
+ "content": {
8454
+ "application/json": {
8455
+ "schema": {
8456
+ "$ref": "#/components/schemas/Error"
8457
+ }
8458
+ }
8459
+ }
8460
+ },
8461
+ "429": {
8462
+ "description": "`rate_limited`: the API key made more than 10 requests in the last second. Wait for the `Retry-After` seconds",
8463
+ "content": {
8464
+ "application/json": {
8465
+ "schema": {
8466
+ "$ref": "#/components/schemas/Error"
8467
+ }
8468
+ }
8469
+ }
8470
+ },
8471
+ "502": {
8472
+ "description": "`provider_unavailable`: Stripe refused or couldn't be reached, like when its customer portal isn't configured",
8473
+ "content": {
8474
+ "application/json": {
8475
+ "schema": {
8476
+ "$ref": "#/components/schemas/Error"
8477
+ }
8478
+ }
8479
+ }
8480
+ }
8481
+ }
8482
+ }
8483
+ },
7501
8484
  "/v1/customers/{id}/portal-links/send": {
7502
8485
  "post": {
7503
8486
  "tags": [
@@ -9140,7 +10123,7 @@
9140
10123
  }
9141
10124
  },
9142
10125
  "422": {
9143
- "description": "`validation_error`: invalid body. `invalid_api_key`: not a Stripe secret or restricted key, or Stripe rejected it. `key_mode_mismatch`: a test mode key for a live product, or the opposite. `missing_permission`: the key lacks required permissions, listed in the message",
10126
+ "description": "`validation_error`: invalid body. `invalid_api_key`: not a Stripe secret or restricted key, or Stripe rejected it. `key_mode_mismatch`: a test mode key for a live product, or the opposite. `missing_permission`: the key lacks required permissions, listed in the message. Optional ones aren't required: without Refunds: Write, it connects with `canRefund` false",
9144
10127
  "content": {
9145
10128
  "application/json": {
9146
10129
  "schema": {
@@ -9269,6 +10252,110 @@
9269
10252
  }
9270
10253
  }
9271
10254
  }
10255
+ },
10256
+ "patch": {
10257
+ "tags": [
10258
+ "Integrations"
10259
+ ],
10260
+ "operationId": "updateIntegration",
10261
+ "summary": "Update an integration",
10262
+ "description": "Changes the settings of the integration, for every product of its mode. Doesn't accept tokens: only API keys and sessions.\n\n**Required role permission**: `integration:update` when called with a session, granted by the member's role. API keys aren't limited by roles.",
10263
+ "x-required-permissions": [
10264
+ "integration:update"
10265
+ ],
10266
+ "parameters": [
10267
+ {
10268
+ "schema": {
10269
+ "type": "string",
10270
+ "example": "i8Fz4QnRt"
10271
+ },
10272
+ "required": true,
10273
+ "name": "id",
10274
+ "in": "path"
10275
+ }
10276
+ ],
10277
+ "requestBody": {
10278
+ "required": true,
10279
+ "content": {
10280
+ "application/json": {
10281
+ "schema": {
10282
+ "$ref": "#/components/schemas/UpdateIntegration"
10283
+ }
10284
+ }
10285
+ }
10286
+ },
10287
+ "responses": {
10288
+ "200": {
10289
+ "description": "Updated integration",
10290
+ "content": {
10291
+ "application/json": {
10292
+ "schema": {
10293
+ "$ref": "#/components/schemas/Integration"
10294
+ }
10295
+ }
10296
+ }
10297
+ },
10298
+ "401": {
10299
+ "description": "`unauthorized`: no credentials. `invalid_api_key`: the API key doesn't exist, was revoked or has expired. `invalid_token`: the token's signature is invalid, it has expired, or the resource it's scoped to was deleted",
10300
+ "content": {
10301
+ "application/json": {
10302
+ "schema": {
10303
+ "$ref": "#/components/schemas/Error"
10304
+ }
10305
+ }
10306
+ }
10307
+ },
10308
+ "402": {
10309
+ "description": "`plan_limit_exceeded`: the organization stayed over its plan's limits after the grace period, its API is blocked until it upgrades. `plan_limit_reached`: creating the resource would go over the plan's limit",
10310
+ "content": {
10311
+ "application/json": {
10312
+ "schema": {
10313
+ "$ref": "#/components/schemas/Error"
10314
+ }
10315
+ }
10316
+ }
10317
+ },
10318
+ "403": {
10319
+ "description": "`token_not_allowed`: the endpoint doesn't accept tokens, or not of this type. `insufficient_scope`: the token lacks the permission. `no_active_organization` or `forbidden`: the session has no organization, or isn't a member of the one requested",
10320
+ "content": {
10321
+ "application/json": {
10322
+ "schema": {
10323
+ "$ref": "#/components/schemas/Error"
10324
+ }
10325
+ }
10326
+ }
10327
+ },
10328
+ "404": {
10329
+ "description": "Integration not found",
10330
+ "content": {
10331
+ "application/json": {
10332
+ "schema": {
10333
+ "$ref": "#/components/schemas/Error"
10334
+ }
10335
+ }
10336
+ }
10337
+ },
10338
+ "422": {
10339
+ "description": "`validation_error`: invalid body. `missing_permission`: turning `autoRefund` on while the API key lacks the Refunds: Write permission",
10340
+ "content": {
10341
+ "application/json": {
10342
+ "schema": {
10343
+ "$ref": "#/components/schemas/Error"
10344
+ }
10345
+ }
10346
+ }
10347
+ },
10348
+ "429": {
10349
+ "description": "`rate_limited`: the API key made more than 10 requests in the last second. Wait for the `Retry-After` seconds",
10350
+ "content": {
10351
+ "application/json": {
10352
+ "schema": {
10353
+ "$ref": "#/components/schemas/Error"
10354
+ }
10355
+ }
10356
+ }
10357
+ }
10358
+ }
9272
10359
  }
9273
10360
  },
9274
10361
  "/v1/integrations/{id}/check": {