@lunch-money/v2-api-spec 2.11.1-preview.5 → 2.11.1-preview.9

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.
package/README.md CHANGED
@@ -14,6 +14,7 @@ After installation, the package provides:
14
14
 
15
15
  - **OpenAPI Specification**: `node_modules/@lunch-money/v2-api-spec/lunch-money-api-v2.yaml`
16
16
  - **Version History**: `node_modules/@lunch-money/v2-api-spec/version-history.md`
17
+ - **OAuth scope catalog**: `@lunch-money/v2-api-spec/oauth-scopes`
17
18
 
18
19
  ## Usage
19
20
 
@@ -26,6 +27,22 @@ const specPath = require.resolve('@lunch-money/v2-api-spec/lunch-money-api-v2.ya
26
27
  const spec = fs.readFileSync(specPath, 'utf8');
27
28
  ```
28
29
 
30
+ ### Using the OAuth scope catalog
31
+
32
+ The package also provides immutable OAuth scope and scope-group definitions.
33
+ API scopes identify the V2 operations they authorize by `operationId`.
34
+
35
+ ```javascript
36
+ const {
37
+ getOAuthScopeDefinition,
38
+ matchOAuthScopeGroup,
39
+ oauthScopeGroups,
40
+ oauthScopes,
41
+ } = require('@lunch-money/v2-api-spec/oauth-scopes');
42
+
43
+ const transactionRead = getOAuthScopeDefinition('transactions:read');
44
+ ```
45
+
29
46
  ### Using with OpenAPI Tools
30
47
 
31
48
  ```bash
@@ -53,9 +53,11 @@ servers:
53
53
  tags:
54
54
  - name: me
55
55
  description: View details and settings for the current user and account.<p>
56
- Use [/me/account/settings](#tag/me/GET/me/account/settings) and
57
- [/me/user/settings](#tag/me/GET/me/user/settings) for account- and
58
- user-level preferences.
56
+ Use [/me/user/settings](#tag/me/GET/me/user/settings) for user-wide
57
+ preferences, [/me/account/settings](#tag/me/GET/me/account/settings) for
58
+ account-wide preferences, and
59
+ [/me/user/account/settings](#tag/me/GET/me/user/account/settings) for
60
+ preferences specific to the current user and budgeting account.
59
61
  - name: categories
60
62
  description: Work with categories
61
63
  externalDocs:
@@ -3798,6 +3800,7 @@ components:
3798
3800
  - budget_rollover_left_to_budget
3799
3801
 
3800
3802
  weekStartsOnEnum:
3803
+ x-internal: true
3801
3804
  type: string
3802
3805
  title: week starts on enum
3803
3806
  enum:
@@ -3806,6 +3809,7 @@ components:
3806
3809
  description: The day on which a calendar week begins.
3807
3810
 
3808
3811
  monthYearFormatEnum:
3812
+ x-internal: true
3809
3813
  type: string
3810
3814
  title: month and year format enum
3811
3815
  description: Format string for displaying month and year values
@@ -3832,6 +3836,7 @@ components:
3832
3836
  - "YYYY/MM"
3833
3837
 
3834
3838
  monthDayYearFormatEnum:
3839
+ x-internal: true
3835
3840
  type: string
3836
3841
  title: month, day and year format enum
3837
3842
  description: Format string for displaying full dates in the
@@ -3889,6 +3894,7 @@ components:
3889
3894
  - "M/DD/YYYY"
3890
3895
 
3891
3896
  monthDayFormatEnum:
3897
+ x-internal: true
3892
3898
  type: string
3893
3899
  title: month and day format enum
3894
3900
  description: Format string for displaying month and day values
@@ -3931,6 +3937,98 @@ components:
3931
3937
  - "DD.M"
3932
3938
  - "DD/M"
3933
3939
 
3940
+ localeEnum:
3941
+ type: string
3942
+ title: locale enum
3943
+ description: Locale used for formatting numbers and currency amounts in
3944
+ the Lunch Money app.
3945
+ example: en-US
3946
+ enum:
3947
+ - sq-AL # Albanian (Albania)
3948
+ - be-BY # Belarusian (Belarus)
3949
+ - bg-BG # Bulgarian (Bulgaria)
3950
+ - ca-ES # Catalan (Spain)
3951
+ - zh-CN # Chinese (Simplified) (China)
3952
+ - zh-HK # Chinese (Traditional) (Hong Kong)
3953
+ - zh-TW # Chinese (Traditional) (Taiwan)
3954
+ - hr-HR # Croatian (Croatia)
3955
+ - cs-CZ # Czech (Czech Republic)
3956
+ - da-DK # Danish (Denmark)
3957
+ - nl-BE # Dutch (Belgium)
3958
+ - nl-NL # Dutch (Netherlands)
3959
+ - en-AU # English (Australia)
3960
+ - en-CA # English (Canada)
3961
+ - en-IN # English (India)
3962
+ - en-IE # English (Ireland)
3963
+ - en-MT # English (Malta)
3964
+ - en-NZ # English (New Zealand)
3965
+ - en-PH # English (Philippines)
3966
+ - en-SG # English (Singapore)
3967
+ - en-ZA # English (South Africa)
3968
+ - en-GB # English (United Kingdom)
3969
+ - en-US # English (United States)
3970
+ - et-EE # Estonian (Estonia)
3971
+ - fi-FI # Finnish (Finland)
3972
+ - fr-BE # French (Belgium)
3973
+ - fr-CA # French (Canada)
3974
+ - fr-FR # French (France)
3975
+ - fr-LU # French (Luxembourg)
3976
+ - fr-CH # French (Switzerland)
3977
+ - de-AT # German (Austria)
3978
+ - de-DE # German (Germany)
3979
+ - de-LU # German (Luxembourg)
3980
+ - de-CH # German (Switzerland)
3981
+ - el-CY # Greek (Cyprus)
3982
+ - el-GR # Greek (Greece)
3983
+ - iw-IL # Hebrew (Israel); modern replacement: he-IL
3984
+ - hi-IN # Hindi (India)
3985
+ - hu-HU # Hungarian (Hungary)
3986
+ - is-IS # Icelandic (Iceland)
3987
+ - in-ID # Indonesian (Indonesia); modern replacement: id-ID
3988
+ - ga-IE # Irish (Ireland)
3989
+ - it-IT # Italian (Italy)
3990
+ - it-CH # Italian (Switzerland)
3991
+ - ja-JP # Japanese (Japan)
3992
+ - ko-KR # Korean (South Korea)
3993
+ - lv-LV # Latvian (Latvia)
3994
+ - lt-LT # Lithuanian (Lithuania)
3995
+ - mk-MK # Macedonian (Macedonia)
3996
+ - ms-MY # Malay (Malaysia)
3997
+ - mt-MT # Maltese (Malta)
3998
+ - no-NO # Norwegian (Norway)
3999
+ - pl-PL # Polish (Poland)
4000
+ - pt-BR # Portuguese (Brazil)
4001
+ - pt-PT # Portuguese (Portugal)
4002
+ - ro-RO # Romanian (Romania)
4003
+ - ru-RU # Russian (Russia)
4004
+ - sk-SK # Slovak (Slovakia)
4005
+ - sl-SI # Slovenian (Slovenia)
4006
+ - es-AR # Spanish (Argentina)
4007
+ - es-BO # Spanish (Bolivia)
4008
+ - es-CL # Spanish (Chile)
4009
+ - es-CO # Spanish (Colombia)
4010
+ - es-CR # Spanish (Costa Rica)
4011
+ - es-DO # Spanish (Dominican Republic)
4012
+ - es-EC # Spanish (Ecuador)
4013
+ - es-SV # Spanish (El Salvador)
4014
+ - es-GT # Spanish (Guatemala)
4015
+ - es-HN # Spanish (Honduras)
4016
+ - es-MX # Spanish (Mexico)
4017
+ - es-NI # Spanish (Nicaragua)
4018
+ - es-PA # Spanish (Panama)
4019
+ - es-PY # Spanish (Paraguay)
4020
+ - es-PE # Spanish (Peru)
4021
+ - es-PR # Spanish (Puerto Rico)
4022
+ - es-ES # Spanish (Spain)
4023
+ - es-US # Spanish (United States)
4024
+ - es-UY # Spanish (Uruguay)
4025
+ - es-VE # Spanish (Venezuela)
4026
+ - sv-SE # Swedish (Sweden)
4027
+ - th-TH # Thai (Thailand)
4028
+ - tr-TR # Turkish (Turkey)
4029
+ - uk-UA # Ukrainian (Ukraine)
4030
+ - vi-VN # Vietnamese (Vietnam)
4031
+
3934
4032
  accountSettingsObject:
3935
4033
  type: object
3936
4034
  title: account settings object
@@ -3954,13 +4052,19 @@ components:
3954
4052
  description: Display name of the budgeting account in the Lunch Money
3955
4053
  app.
3956
4054
  locale:
3957
- type: string
3958
- description: Locale used for formatting dates and numbers in the
3959
- Lunch Money app (for example, `en-US`). When no locale is explicitly
3960
- stored for the account, the effective locale is derived from the
3961
- `default_locale` associated with `primary_currency` in the
3962
- currencies table (for example, `usd` → `en-US`, `cad` → `en-CA`,
3963
- `gbp` → `en-GB`).
4055
+ allOf:
4056
+ - $ref: "#/components/schemas/localeEnum"
4057
+ example: en-US
4058
+ description: Locale used for formatting numbers and currency amounts
4059
+ in the Lunch Money app (for example, `en-US`). See
4060
+ [Supported Locales](https://lunchmoney.dev/v2/locales) for accepted
4061
+ values. Date presentation is configured separately through
4062
+ [GET /me/user/settings](#tag/me/GET/me/user/settings) and
4063
+ [PUT /me/user/settings](#tag/me/PUT/me/user/settings). When no
4064
+ locale is explicitly stored for the account, the effective locale
4065
+ is derived from the `default_locale` associated with
4066
+ `primary_currency` in the currencies table (for example, `usd` →
4067
+ `en-US`, `cad` → `en-CA`, `gbp` → `en-GB`).
3964
4068
  auto_create_category_rules:
3965
4069
  type: boolean
3966
4070
  default: true
@@ -3971,16 +4075,11 @@ components:
3971
4075
  default: true
3972
4076
  description: If `true`, suggested transaction rules are created
3973
4077
  automatically.
3974
- auto_review_transaction_on_update:
4078
+ include_pending_in_totals:
3975
4079
  type: boolean
3976
4080
  default: true
3977
- description: If `true`, transactions are marked as reviewed when
3978
- updated.
3979
- auto_review_transaction_on_creation:
3980
- type: boolean
3981
- default: true
3982
- description: If `true`, transactions are marked as reviewed when
3983
- created.
4081
+ description: If `true`, pending transactions are included in account
4082
+ totals.
3984
4083
  required:
3985
4084
  - primary_currency
3986
4085
  - supported_currencies
@@ -3988,13 +4087,13 @@ components:
3988
4087
  - locale
3989
4088
  - auto_create_category_rules
3990
4089
  - auto_create_suggested_transaction_rules
3991
- - auto_review_transaction_on_update
3992
- - auto_review_transaction_on_creation
4090
+ - include_pending_in_totals
3993
4091
 
3994
4092
  updateAccountSettingsRequestObject:
3995
4093
  x-internal: true
3996
4094
  type: object
3997
4095
  additionalProperties: false
4096
+ minProperties: 1
3998
4097
  description: Request body for updating account settings. Include at least
3999
4098
  one property to update.
4000
4099
  properties:
@@ -4016,9 +4115,15 @@ components:
4016
4115
  description: If set, updates the display name of the budgeting account.
4017
4116
  x-updatable: true
4018
4117
  locale:
4019
- type: string
4020
- description: If set, updates the locale used for formatting dates and
4021
- numbers in the Lunch Money app.
4118
+ allOf:
4119
+ - $ref: "#/components/schemas/localeEnum"
4120
+ example: en-US
4121
+ description: If set, updates the locale used for formatting numbers
4122
+ and currency amounts in the Lunch Money app. See
4123
+ [Supported Locales](https://lunchmoney.dev/v2/locales) for accepted
4124
+ values. Date presentation is configured separately through
4125
+ [GET /me/user/settings](#tag/me/GET/me/user/settings) and
4126
+ [PUT /me/user/settings](#tag/me/PUT/me/user/settings).
4022
4127
  x-updatable: true
4023
4128
  auto_create_category_rules:
4024
4129
  type: boolean
@@ -4030,15 +4135,70 @@ components:
4030
4135
  description: If set, updates whether suggested transaction rules are
4031
4136
  created automatically.
4032
4137
  x-updatable: true
4138
+ include_pending_in_totals:
4139
+ type: boolean
4140
+ description: If set, updates whether pending transactions are included
4141
+ in account totals.
4142
+ x-updatable: true
4143
+
4144
+ userAccountSettingsObject:
4145
+ type: object
4146
+ title: user account settings object
4147
+ additionalProperties: false
4148
+ description: Settings specific to the authorized user within the current
4149
+ budgeting account.
4150
+ properties:
4151
+ auto_review_transaction_on_update:
4152
+ type: boolean
4153
+ default: true
4154
+ description: If `true`, transactions are marked as reviewed when their
4155
+ date, category, payee, amount, account, or notes are changed.
4156
+ auto_review_transaction_on_creation:
4157
+ type: boolean
4158
+ default: true
4159
+ description: If `true`, new manual transactions start as reviewed. If
4160
+ `false`, they start as unreviewed.
4161
+ default_manual_account_id:
4162
+ type: integer
4163
+ nullable: true
4164
+ description: Manual account selected by default when the user creates a
4165
+ manual transaction in the current budgeting account. Must identify a
4166
+ manual account returned by
4167
+ [GET /manual_accounts](#tag/manual_accounts/GET/manual_accounts) for
4168
+ the current budgeting account. Set to `null` to clear the selection.
4169
+ required:
4170
+ - auto_review_transaction_on_update
4171
+ - auto_review_transaction_on_creation
4172
+ - default_manual_account_id
4173
+
4174
+ updateUserAccountSettingsRequestObject:
4175
+ x-internal: true
4176
+ type: object
4177
+ additionalProperties: false
4178
+ minProperties: 1
4179
+ description: Request body for updating settings specific to the authorized
4180
+ user within the current budgeting account. Include at least one property
4181
+ to update.
4182
+ properties:
4033
4183
  auto_review_transaction_on_update:
4034
4184
  type: boolean
4035
4185
  description: If set, updates whether transactions are marked as reviewed
4036
- when updated.
4186
+ when their date, category, payee, amount, account, or notes are
4187
+ changed.
4037
4188
  x-updatable: true
4038
4189
  auto_review_transaction_on_creation:
4039
4190
  type: boolean
4040
- description: If set, updates whether transactions are marked as reviewed
4041
- when created.
4191
+ description: If set, updates whether new manual transactions start as
4192
+ reviewed or unreviewed.
4193
+ x-updatable: true
4194
+ default_manual_account_id:
4195
+ type: integer
4196
+ nullable: true
4197
+ description: If set, updates the manual account selected by default when
4198
+ the user creates a manual transaction in the current budgeting
4199
+ account. Must identify a manual account returned by
4200
+ [GET /manual_accounts](#tag/manual_accounts/GET/manual_accounts) for
4201
+ the current budgeting account. Set to `null` to clear the selection.
4042
4202
  x-updatable: true
4043
4203
 
4044
4204
  userSettingsObject:
@@ -4114,6 +4274,7 @@ components:
4114
4274
  x-internal: true
4115
4275
  type: object
4116
4276
  additionalProperties: false
4277
+ minProperties: 1
4117
4278
  description: Request body for updating user settings. Include at least one
4118
4279
  property to update.
4119
4280
  properties:
@@ -4950,11 +5111,14 @@ paths:
4950
5111
  summary: Get current user
4951
5112
  description: |-
4952
5113
  Get details about the user associated with the supplied authorization
4953
- token.<p> For account- and user-level preferences, prefer
4954
- [/me/account/settings](#tag/me/GET/me/account/settings) and
4955
- [/me/user/settings](#tag/me/GET/me/user/settings). Properties such as
4956
- `primary_currency` and `budget_name` remain on this response for
4957
- backwards compatibility.
5114
+ token.<p> Use [/me/user/settings](#tag/me/GET/me/user/settings) for
5115
+ user-wide preferences,
5116
+ [/me/account/settings](#tag/me/GET/me/account/settings) for account-wide
5117
+ preferences, and
5118
+ [/me/user/account/settings](#tag/me/GET/me/user/account/settings) for
5119
+ preferences specific to the current user and budgeting account.
5120
+ Properties such as `primary_currency` and `budget_name` remain on this
5121
+ response for backwards compatibility.
4958
5122
  operationId: getMe
4959
5123
  parameters: []
4960
5124
  responses:
@@ -4983,8 +5147,9 @@ paths:
4983
5147
  tags:
4984
5148
  - me
4985
5149
  summary: Get account settings
4986
- description: |-
4987
- Returns account-level settings for the budgeting account associated with the authorized API token.
5150
+ description: >-
5151
+ Returns settings for the current budgeting account. These settings apply
5152
+ regardless of which user is accessing the account.
4988
5153
  operationId: getAccountSettings
4989
5154
  responses:
4990
5155
  "200":
@@ -5003,8 +5168,7 @@ paths:
5003
5168
  locale: en-US
5004
5169
  auto_create_category_rules: true
5005
5170
  auto_create_suggested_transaction_rules: true
5006
- auto_review_transaction_on_update: true
5007
- auto_review_transaction_on_creation: true
5171
+ include_pending_in_totals: true
5008
5172
  "401":
5009
5173
  $ref: "#/components/responses/unauthorizedToken"
5010
5174
  "429":
@@ -5017,11 +5181,10 @@ paths:
5017
5181
  summary: Update account settings
5018
5182
  description: |-
5019
5183
  Updates account-level settings for the budgeting account
5020
- associated with the authorized API token.<p> You may submit the response from a
5021
- `GET /me/account/settings` as the request body; however, only certain
5022
- properties can be updated.<p> It is also possible to provide only the
5023
- properties to be updated in the request body, as long as the request
5024
- includes at least one updatable property.
5184
+ associated with the authorized API token. Submit the full response from
5185
+ `GET /me/account/settings` with one or more properties changed, or
5186
+ provide only the properties to update. The request body must include at
5187
+ least one property.
5025
5188
  operationId: updateAccountSettings
5026
5189
  requestBody:
5027
5190
  required: true
@@ -5036,7 +5199,7 @@ paths:
5036
5199
  Update automation preferences:
5037
5200
  value:
5038
5201
  auto_create_category_rules: false
5039
- auto_review_transaction_on_creation: false
5202
+ include_pending_in_totals: false
5040
5203
  responses:
5041
5204
  "200":
5042
5205
  description: Account settings updated successfully
@@ -5061,8 +5224,10 @@ paths:
5061
5224
  tags:
5062
5225
  - me
5063
5226
  summary: Get user settings
5064
- description: |-
5065
- Returns user-level display and formatting preferences for the user associated with the authorized API token.
5227
+ description: >-
5228
+ Returns display and formatting preferences for the authorized user.
5229
+ These settings apply across every budgeting account the user owns or
5230
+ collaborates on.
5066
5231
  operationId: getUserSettings
5067
5232
  responses:
5068
5233
  "200":
@@ -5093,11 +5258,10 @@ paths:
5093
5258
  summary: Update user settings
5094
5259
  description: |-
5095
5260
  Updates user-level display and formatting preferences for the user
5096
- associated with the authorized API token.<p> You may submit the response
5097
- from a `GET /me/user/settings` as the request body; however, only
5098
- certain properties can be updated.<p> It is also possible to provide
5099
- only the properties to be updated in the request body, as long as the
5100
- request includes at least one updatable property.<p> Updating
5261
+ associated with the authorized API token. Submit the full response from
5262
+ `GET /me/user/settings` with one or more properties changed, or provide
5263
+ only the properties to update. The request body must include at least
5264
+ one property.<p> Updating
5101
5265
  `show_debits_as_negative` affects display in the Lunch Money app only.
5102
5266
  Amount fields in API responses always use positive values for debits and
5103
5267
  negative values for credits.
@@ -5137,6 +5301,88 @@ paths:
5137
5301
  $ref: "#/components/responses/rateLimited"
5138
5302
  "500":
5139
5303
  $ref: "#/components/responses/serverError"
5304
+ /me/user/account/settings:
5305
+ get:
5306
+ tags:
5307
+ - me
5308
+ summary: Get user account settings
5309
+ description: >-
5310
+ Returns settings specific to the authorized user within the current
5311
+ budgeting account. These settings do not affect other users or the
5312
+ authorized user's settings in other budgeting accounts.
5313
+ operationId: getUserAccountSettings
5314
+ responses:
5315
+ "200":
5316
+ description: Settings for the authorized user in the current budgeting
5317
+ account
5318
+ content:
5319
+ application/json:
5320
+ schema:
5321
+ $ref: "#/components/schemas/userAccountSettingsObject"
5322
+ example:
5323
+ auto_review_transaction_on_update: true
5324
+ auto_review_transaction_on_creation: true
5325
+ default_manual_account_id: null
5326
+ "401":
5327
+ $ref: "#/components/responses/unauthorizedToken"
5328
+ "429":
5329
+ $ref: "#/components/responses/rateLimited"
5330
+ "500":
5331
+ $ref: "#/components/responses/serverError"
5332
+ put:
5333
+ tags:
5334
+ - me
5335
+ summary: Update user account settings
5336
+ description: |-
5337
+ Updates settings specific to the authorized user within the current
5338
+ budgeting account. Submit the full response from
5339
+ `GET /me/user/account/settings` with one or more properties changed, or
5340
+ provide only the properties to update. The request body must include at
5341
+ least one property.
5342
+ operationId: updateUserAccountSettings
5343
+ requestBody:
5344
+ required: true
5345
+ content:
5346
+ application/json:
5347
+ schema:
5348
+ $ref: "#/components/schemas/updateUserAccountSettingsRequestObject"
5349
+ examples:
5350
+ Update review preference:
5351
+ value:
5352
+ auto_review_transaction_on_creation: false
5353
+ Clear default manual account:
5354
+ value:
5355
+ default_manual_account_id: null
5356
+ responses:
5357
+ "200":
5358
+ description: User account settings updated successfully
5359
+ content:
5360
+ application/json:
5361
+ schema:
5362
+ $ref: "#/components/schemas/userAccountSettingsObject"
5363
+ examples:
5364
+ Update review preference:
5365
+ value:
5366
+ auto_review_transaction_on_update: true
5367
+ auto_review_transaction_on_creation: false
5368
+ default_manual_account_id: null
5369
+ Clear default manual account:
5370
+ value:
5371
+ auto_review_transaction_on_update: true
5372
+ auto_review_transaction_on_creation: true
5373
+ default_manual_account_id: null
5374
+ "400":
5375
+ description: Invalid request body
5376
+ content:
5377
+ application/json:
5378
+ schema:
5379
+ $ref: "#/components/schemas/errorResponseObject"
5380
+ "401":
5381
+ $ref: "#/components/responses/unauthorizedToken"
5382
+ "429":
5383
+ $ref: "#/components/responses/rateLimited"
5384
+ "500":
5385
+ $ref: "#/components/responses/serverError"
5140
5386
  /summary:
5141
5387
  get:
5142
5388
  tags:
@@ -0,0 +1,9 @@
1
+ import type { OAuthCatalogDefinition, OAuthCatalogValidationOptions, OAuthCatalogValidationResult, OAuthScopeDefinition, OAuthScopeGroupDefinition, OAuthScopeGroupMatch, OAuthScopeName } from './types';
2
+ export declare const oauthScopes: readonly OAuthScopeDefinition[];
3
+ export declare const oauthScopeGroups: readonly OAuthScopeGroupDefinition[];
4
+ export declare const oauthCatalog: OAuthCatalogDefinition;
5
+ export declare function getOAuthScopeDefinition(name: OAuthScopeName): OAuthScopeDefinition | undefined;
6
+ export declare function getOAuthScopeGroup(id: string): OAuthScopeGroupDefinition | undefined;
7
+ export declare function getOAuthScopeForOperationId(operationId: string): OAuthScopeDefinition | undefined;
8
+ export declare function matchOAuthScopeGroup(scopeNames: readonly OAuthScopeName[]): OAuthScopeGroupMatch;
9
+ export declare function validateOAuthCatalog(definition: OAuthCatalogDefinition, options?: OAuthCatalogValidationOptions): OAuthCatalogValidationResult;
@@ -0,0 +1,528 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.oauthCatalog = exports.oauthScopeGroups = exports.oauthScopes = void 0;
4
+ exports.getOAuthScopeDefinition = getOAuthScopeDefinition;
5
+ exports.getOAuthScopeGroup = getOAuthScopeGroup;
6
+ exports.getOAuthScopeForOperationId = getOAuthScopeForOperationId;
7
+ exports.matchOAuthScopeGroup = matchOAuthScopeGroup;
8
+ exports.validateOAuthCatalog = validateOAuthCatalog;
9
+ const freezeScope = (scope) => Object.freeze({ ...scope, operationIds: Object.freeze([...scope.operationIds]) });
10
+ const freezeGroup = (group) => Object.freeze({ ...group, scopes: Object.freeze([...group.scopes]) });
11
+ const scopeDefinitions = [
12
+ {
13
+ name: 'openid',
14
+ description: 'Verify your identity with Lunch Money',
15
+ category: 'identity',
16
+ operationIds: [],
17
+ status: 'active',
18
+ },
19
+ {
20
+ name: 'email',
21
+ description: 'View your email address and whether it has been verified',
22
+ category: 'identity',
23
+ operationIds: [],
24
+ status: 'active',
25
+ },
26
+ {
27
+ name: 'profile',
28
+ description: 'View your Lunch Money profile information',
29
+ category: 'identity',
30
+ operationIds: [],
31
+ status: 'active',
32
+ },
33
+ {
34
+ name: 'offline_access',
35
+ description: 'Continue accessing Lunch Money when you are not actively using the app',
36
+ category: 'offline',
37
+ operationIds: [],
38
+ status: 'active',
39
+ },
40
+ {
41
+ name: 'me:read',
42
+ description: 'View your Lunch Money identity and account, user, and budget settings',
43
+ category: 'api',
44
+ operationIds: ['getMe', 'getAccountSettings', 'getUserSettings', 'getUserAccountSettings'],
45
+ status: 'active',
46
+ },
47
+ {
48
+ name: 'me:update',
49
+ description: 'Update your Lunch Money account, user, and budget settings',
50
+ category: 'api',
51
+ operationIds: ['updateAccountSettings', 'updateUserSettings', 'updateUserAccountSettings'],
52
+ status: 'active',
53
+ },
54
+ {
55
+ name: 'summary:read',
56
+ description: 'View your budget summary',
57
+ category: 'api',
58
+ operationIds: ['getBudgetSummary'],
59
+ status: 'active',
60
+ },
61
+ {
62
+ name: 'categories:read',
63
+ description: 'View categories and category groups',
64
+ category: 'api',
65
+ operationIds: ['getAllCategories', 'getCategoryById'],
66
+ status: 'active',
67
+ },
68
+ {
69
+ name: 'categories:create',
70
+ description: 'Create categories and category groups',
71
+ category: 'api',
72
+ operationIds: ['createCategory'],
73
+ status: 'active',
74
+ },
75
+ {
76
+ name: 'categories:update',
77
+ description: 'Update categories and category groups',
78
+ category: 'api',
79
+ operationIds: ['updateCategory'],
80
+ status: 'active',
81
+ },
82
+ {
83
+ name: 'categories:delete',
84
+ description: 'Delete categories and category groups',
85
+ category: 'api',
86
+ operationIds: ['deleteCategory'],
87
+ status: 'active',
88
+ },
89
+ {
90
+ name: 'crypto_manual:read',
91
+ description: 'View supported cryptocurrencies and manually managed cryptocurrency balances',
92
+ category: 'api',
93
+ operationIds: ['getAllCryptocurrencies', 'getAllCryptoManual', 'getCryptoManualById'],
94
+ status: 'active',
95
+ },
96
+ {
97
+ name: 'crypto_manual:create',
98
+ description: 'Add supported cryptocurrencies and create manually managed cryptocurrency balances',
99
+ category: 'api',
100
+ operationIds: ['createCryptocurrency', 'createCryptoManual'],
101
+ status: 'active',
102
+ },
103
+ {
104
+ name: 'crypto_manual:update',
105
+ description: 'Update manually managed cryptocurrency balances',
106
+ category: 'api',
107
+ operationIds: ['updateCryptoManual'],
108
+ status: 'active',
109
+ },
110
+ {
111
+ name: 'crypto_manual:delete',
112
+ description: 'Delete manually managed cryptocurrency balances',
113
+ category: 'api',
114
+ operationIds: ['deleteCryptoManual'],
115
+ status: 'active',
116
+ },
117
+ {
118
+ name: 'crypto_synced:read',
119
+ description: 'View synced cryptocurrency accounts and balances',
120
+ category: 'api',
121
+ operationIds: ['getAllCryptoSynced', 'getCryptoSyncedById', 'getCryptoSyncedBalanceBySymbol'],
122
+ status: 'active',
123
+ },
124
+ {
125
+ name: 'crypto_synced:update',
126
+ description: 'Refresh balances for synced cryptocurrency accounts',
127
+ category: 'api',
128
+ operationIds: ['refreshCryptoSynced'],
129
+ status: 'active',
130
+ },
131
+ {
132
+ name: 'balance_history:read',
133
+ description: 'View balance history for accounts and synced cryptocurrency balances',
134
+ category: 'api',
135
+ operationIds: ['getBalanceHistory', 'getBalanceHistoryForAccount', 'getBalanceHistoryForCryptoSynced'],
136
+ status: 'active',
137
+ },
138
+ {
139
+ name: 'balance_history:update',
140
+ description: 'Create or update balance history and details for deleted accounts',
141
+ category: 'api',
142
+ operationIds: ['upsertBalanceHistoryForAccount', 'upsertBalanceHistoryForCryptoSynced', 'updateBalanceHistoryDetails'],
143
+ status: 'active',
144
+ },
145
+ {
146
+ name: 'balance_history:delete',
147
+ description: 'Delete balance history for accounts and synced cryptocurrency balances',
148
+ category: 'api',
149
+ operationIds: ['deleteBalanceHistoryForAccount', 'deleteBalanceHistoryForCryptoSynced', 'deleteBalanceHistoryEntry'],
150
+ status: 'active',
151
+ },
152
+ {
153
+ name: 'manual_accounts:read',
154
+ description: 'View manually managed accounts',
155
+ category: 'api',
156
+ operationIds: ['getAllManualAccounts', 'getManualAccountById'],
157
+ status: 'active',
158
+ },
159
+ {
160
+ name: 'manual_accounts:create',
161
+ description: 'Create manually managed accounts',
162
+ category: 'api',
163
+ operationIds: ['createManualAccount'],
164
+ status: 'active',
165
+ },
166
+ {
167
+ name: 'manual_accounts:update',
168
+ description: 'Update manually managed accounts',
169
+ category: 'api',
170
+ operationIds: ['updateManualAccount'],
171
+ status: 'active',
172
+ },
173
+ {
174
+ name: 'manual_accounts:delete',
175
+ description: 'Delete manually managed accounts',
176
+ category: 'api',
177
+ operationIds: ['deleteManualAccount'],
178
+ status: 'active',
179
+ },
180
+ {
181
+ name: 'plaid_accounts:read',
182
+ description: 'View accounts connected through Plaid',
183
+ category: 'api',
184
+ operationIds: ['getAllPlaidAccounts', 'getPlaidAccountById'],
185
+ status: 'active',
186
+ },
187
+ {
188
+ name: 'plaid_accounts:update',
189
+ description: 'Request a Plaid account refresh, which may import new transactions',
190
+ category: 'api',
191
+ operationIds: ['triggerPlaidAccountFetch'],
192
+ status: 'active',
193
+ },
194
+ {
195
+ name: 'transactions:read',
196
+ description: 'View transactions, including split and group information and attachment metadata',
197
+ category: 'api',
198
+ operationIds: ['getAllTransactions', 'getTransactionById'],
199
+ status: 'active',
200
+ },
201
+ {
202
+ name: 'transactions:create',
203
+ description: 'Create transactions',
204
+ category: 'api',
205
+ operationIds: ['createNewTransactions'],
206
+ status: 'active',
207
+ },
208
+ {
209
+ name: 'transactions:update',
210
+ description: 'Update, split, unsplit, group, and ungroup transactions',
211
+ category: 'api',
212
+ operationIds: ['updateTransactions', 'updateTransaction', 'groupTransactions', 'ungroupTransactions', 'splitTransaction', 'unsplitTransaction'],
213
+ status: 'active',
214
+ },
215
+ {
216
+ name: 'transactions:delete',
217
+ description: 'Delete transactions',
218
+ category: 'api',
219
+ operationIds: ['deleteTransactions', 'deleteTransactionById'],
220
+ status: 'active',
221
+ },
222
+ {
223
+ name: 'transaction_attachments:read',
224
+ description: 'Get download access to transaction attachments',
225
+ category: 'api',
226
+ operationIds: ['getTransactionAttachmentUrl'],
227
+ status: 'active',
228
+ },
229
+ {
230
+ name: 'transaction_attachments:create',
231
+ description: 'Attach files to transactions',
232
+ category: 'api',
233
+ operationIds: ['attachFileToTransaction'],
234
+ status: 'active',
235
+ },
236
+ {
237
+ name: 'transaction_attachments:delete',
238
+ description: 'Delete transaction attachments',
239
+ category: 'api',
240
+ operationIds: ['deleteTransactionAttachment'],
241
+ status: 'active',
242
+ },
243
+ {
244
+ name: 'tags:read',
245
+ description: 'View tags',
246
+ category: 'api',
247
+ operationIds: ['getAllTags', 'getTagById'],
248
+ status: 'active',
249
+ },
250
+ {
251
+ name: 'tags:create',
252
+ description: 'Create tags',
253
+ category: 'api',
254
+ operationIds: ['createTag'],
255
+ status: 'active',
256
+ },
257
+ {
258
+ name: 'tags:update',
259
+ description: 'Update tags',
260
+ category: 'api',
261
+ operationIds: ['updateTag'],
262
+ status: 'active',
263
+ },
264
+ {
265
+ name: 'tags:delete',
266
+ description: 'Delete tags',
267
+ category: 'api',
268
+ operationIds: ['deleteTag'],
269
+ status: 'active',
270
+ },
271
+ {
272
+ name: 'recurring_items:read',
273
+ description: 'View recurring items',
274
+ category: 'api',
275
+ operationIds: ['getAllRecurring', 'getRecurringById'],
276
+ status: 'active',
277
+ },
278
+ {
279
+ name: 'budgets:read',
280
+ description: 'View budget period settings',
281
+ category: 'api',
282
+ operationIds: ['getBudgetSettings'],
283
+ status: 'active',
284
+ },
285
+ {
286
+ name: 'budgets:update',
287
+ description: 'Create or update budget amounts',
288
+ category: 'api',
289
+ operationIds: ['upsertBudget'],
290
+ status: 'active',
291
+ },
292
+ {
293
+ name: 'budgets:delete',
294
+ description: 'Delete budget amounts',
295
+ category: 'api',
296
+ operationIds: ['deleteBudget'],
297
+ status: 'active',
298
+ },
299
+ ];
300
+ exports.oauthScopes = Object.freeze(scopeDefinitions.map(freezeScope));
301
+ const allApiReadScopes = exports.oauthScopes
302
+ .filter((scope) => scope.category === 'api' && scope.name.endsWith(':read'))
303
+ .map((scope) => scope.name);
304
+ const allApiScopes = exports.oauthScopes
305
+ .filter((scope) => scope.category === 'api')
306
+ .map((scope) => scope.name);
307
+ const groupDefinitions = [
308
+ {
309
+ id: 'read_only',
310
+ name: 'Read only',
311
+ description: 'View all supported Lunch Money data without changing it',
312
+ scopes: allApiReadScopes,
313
+ },
314
+ {
315
+ id: 'transactions',
316
+ name: 'Transactions',
317
+ description: 'Import, export, organize, and manage transactions and their attachments',
318
+ scopes: [
319
+ 'transactions:read',
320
+ 'transactions:create',
321
+ 'transactions:update',
322
+ 'transactions:delete',
323
+ 'transaction_attachments:read',
324
+ 'transaction_attachments:create',
325
+ 'transaction_attachments:delete',
326
+ 'categories:read',
327
+ 'tags:read',
328
+ 'manual_accounts:read',
329
+ 'plaid_accounts:read',
330
+ 'recurring_items:read',
331
+ ],
332
+ },
333
+ {
334
+ id: 'budgeting',
335
+ name: 'Budgeting',
336
+ description: 'Analyze and manage budgets, categories, and tags',
337
+ scopes: [
338
+ 'summary:read',
339
+ 'budgets:read',
340
+ 'budgets:update',
341
+ 'budgets:delete',
342
+ 'categories:read',
343
+ 'categories:create',
344
+ 'categories:update',
345
+ 'categories:delete',
346
+ 'tags:read',
347
+ 'tags:create',
348
+ 'tags:update',
349
+ 'tags:delete',
350
+ 'recurring_items:read',
351
+ ],
352
+ },
353
+ {
354
+ id: 'accounts',
355
+ name: 'Accounts',
356
+ description: 'Manage accounts, balances, and balance history',
357
+ scopes: [
358
+ 'manual_accounts:read',
359
+ 'manual_accounts:create',
360
+ 'manual_accounts:update',
361
+ 'manual_accounts:delete',
362
+ 'plaid_accounts:read',
363
+ 'plaid_accounts:update',
364
+ 'crypto_manual:read',
365
+ 'crypto_manual:create',
366
+ 'crypto_manual:update',
367
+ 'crypto_manual:delete',
368
+ 'crypto_synced:read',
369
+ 'crypto_synced:update',
370
+ 'balance_history:read',
371
+ 'balance_history:update',
372
+ 'balance_history:delete',
373
+ ],
374
+ },
375
+ {
376
+ id: 'organize',
377
+ name: 'Organize',
378
+ description: 'Manage categories and tags used to organize transactions',
379
+ scopes: [
380
+ 'categories:read',
381
+ 'categories:create',
382
+ 'categories:update',
383
+ 'categories:delete',
384
+ 'tags:read',
385
+ 'tags:create',
386
+ 'tags:update',
387
+ 'tags:delete',
388
+ 'recurring_items:read',
389
+ ],
390
+ },
391
+ {
392
+ id: 'full_access',
393
+ name: 'Full access',
394
+ description: 'View and manage all supported Lunch Money data',
395
+ scopes: allApiScopes,
396
+ },
397
+ ];
398
+ exports.oauthScopeGroups = Object.freeze(groupDefinitions.map(freezeGroup));
399
+ const scopeByName = new Map(exports.oauthScopes.map((scope) => [scope.name, scope]));
400
+ const groupById = new Map(exports.oauthScopeGroups.map((group) => [group.id, group]));
401
+ const scopeByOperationId = new Map(exports.oauthScopes.flatMap((scope) => scope.operationIds.map((operationId) => [operationId, scope])));
402
+ exports.oauthCatalog = Object.freeze({
403
+ scopes: exports.oauthScopes,
404
+ groups: exports.oauthScopeGroups,
405
+ });
406
+ function getOAuthScopeDefinition(name) {
407
+ return scopeByName.get(name);
408
+ }
409
+ function getOAuthScopeGroup(id) {
410
+ return groupById.get(id);
411
+ }
412
+ function getOAuthScopeForOperationId(operationId) {
413
+ return scopeByOperationId.get(operationId);
414
+ }
415
+ const canonicalSet = (values) => [...values].sort().join('\u0000');
416
+ const SCOPE_NAME_PATTERN = /^(?:openid|email|profile|offline_access|[a-z][a-z0-9_]*:(?:read|create|update|delete))$/;
417
+ const GROUP_ID_PATTERN = /^[a-z][a-z0-9_]*$/;
418
+ function matchOAuthScopeGroup(scopeNames) {
419
+ const names = new Set(scopeNames);
420
+ if (names.size !== scopeNames.length)
421
+ return { status: 'no_match', reason: 'duplicate_scope' };
422
+ if (scopeNames.some((name) => !getOAuthScopeDefinition(name))) {
423
+ return { status: 'no_match', reason: 'unknown_scope' };
424
+ }
425
+ const candidate = canonicalSet(scopeNames);
426
+ const group = exports.oauthScopeGroups.find((definition) => canonicalSet(definition.scopes) === candidate);
427
+ return group ? { status: 'matched', group } : { status: 'no_match', reason: 'no_exact_match' };
428
+ }
429
+ function validateOAuthCatalog(definition, options = {}) {
430
+ const issues = [];
431
+ const scopeNames = new Set();
432
+ const groupIds = new Set();
433
+ const groupSets = new Map();
434
+ const operationOwners = new Map();
435
+ definition.scopes.forEach((scope, index) => {
436
+ const path = `scopes[${index}]`;
437
+ if (!SCOPE_NAME_PATTERN.test(scope.name)) {
438
+ issues.push({ code: 'invalid_scope_name', path: `${path}.name`, message: `Invalid scope name: ${scope.name}` });
439
+ }
440
+ if (!scope.description.trim()) {
441
+ issues.push({ code: 'missing_description', path: `${path}.description`, message: `Scope ${scope.name} requires a description` });
442
+ }
443
+ if (scopeNames.has(scope.name)) {
444
+ issues.push({ code: 'duplicate_scope_name', path: `${path}.name`, message: `Duplicate scope name: ${scope.name}` });
445
+ }
446
+ scopeNames.add(scope.name);
447
+ if (!['identity', 'offline', 'api'].includes(scope.category)) {
448
+ issues.push({ code: 'invalid_category', path: `${path}.category`, message: `Invalid category: ${scope.category}` });
449
+ }
450
+ if (!['active', 'deprecated', 'retired'].includes(scope.status)) {
451
+ issues.push({ code: 'invalid_lifecycle', path: `${path}.status`, message: `Invalid lifecycle: ${scope.status}` });
452
+ }
453
+ if (scope.category === 'api' && scope.status !== 'retired' && scope.operationIds.length === 0) {
454
+ issues.push({ code: 'missing_operation_ids', path: `${path}.operationIds`, message: `API scope ${scope.name} has no operationIds` });
455
+ }
456
+ if (scope.category !== 'api' && scope.operationIds.length > 0) {
457
+ issues.push({ code: 'unexpected_operation_ids', path: `${path}.operationIds`, message: `Protocol scope ${scope.name} cannot own V2 operationIds` });
458
+ }
459
+ const localIds = new Set();
460
+ scope.operationIds.forEach((operationId, operationIndex) => {
461
+ const operationPath = `${path}.operationIds[${operationIndex}]`;
462
+ if (localIds.has(operationId)) {
463
+ issues.push({ code: 'duplicate_operation_id', path: operationPath, message: `Duplicate operationId: ${operationId}` });
464
+ }
465
+ localIds.add(operationId);
466
+ const owner = operationOwners.get(operationId);
467
+ if (owner && owner !== scope.name) {
468
+ issues.push({ code: 'conflicting_operation_id', path: operationPath, message: `${operationId} is assigned to both ${owner} and ${scope.name}` });
469
+ }
470
+ else {
471
+ operationOwners.set(operationId, scope.name);
472
+ }
473
+ if (options.knownOperationIds && !options.knownOperationIds.has(operationId)) {
474
+ issues.push({ code: 'unknown_operation_id', path: operationPath, message: `Unknown V2 operationId: ${operationId}` });
475
+ }
476
+ });
477
+ });
478
+ definition.groups.forEach((group, index) => {
479
+ const path = `groups[${index}]`;
480
+ if (!GROUP_ID_PATTERN.test(group.id)) {
481
+ issues.push({ code: 'invalid_group_id', path: `${path}.id`, message: `Invalid group ID: ${group.id}` });
482
+ }
483
+ if (!group.description.trim()) {
484
+ issues.push({ code: 'missing_description', path: `${path}.description`, message: `Group ${group.id} requires a description` });
485
+ }
486
+ if (groupIds.has(group.id)) {
487
+ issues.push({ code: 'duplicate_group_id', path: `${path}.id`, message: `Duplicate group ID: ${group.id}` });
488
+ }
489
+ groupIds.add(group.id);
490
+ if (group.scopes.length === 0) {
491
+ issues.push({ code: 'empty_group', path: `${path}.scopes`, message: `Group ${group.id} cannot be empty` });
492
+ }
493
+ const members = new Set();
494
+ group.scopes.forEach((scopeName, scopeIndex) => {
495
+ const memberPath = `${path}.scopes[${scopeIndex}]`;
496
+ if (members.has(scopeName)) {
497
+ issues.push({ code: 'duplicate_group_scope', path: memberPath, message: `Duplicate group scope: ${scopeName}` });
498
+ }
499
+ members.add(scopeName);
500
+ const scope = definition.scopes.find((candidate) => candidate.name === scopeName);
501
+ if (!scope) {
502
+ issues.push({ code: 'unknown_group_scope', path: memberPath, message: `Unknown group scope: ${scopeName}` });
503
+ }
504
+ else if (scope.status === 'retired') {
505
+ issues.push({ code: 'retired_group_scope', path: memberPath, message: `Retired scope cannot be grouped: ${scopeName}` });
506
+ }
507
+ else if (scope.category === 'offline') {
508
+ issues.push({ code: 'offline_scope_in_group', path: memberPath, message: `Offline scope cannot be grouped: ${scopeName}` });
509
+ }
510
+ });
511
+ const setKey = canonicalSet(group.scopes);
512
+ const existingGroup = groupSets.get(setKey);
513
+ if (existingGroup) {
514
+ issues.push({ code: 'duplicate_group_members', path: `${path}.scopes`, message: `${group.id} has the same members as ${existingGroup}` });
515
+ }
516
+ else {
517
+ groupSets.set(setKey, group.id);
518
+ }
519
+ });
520
+ if (options.operationIdCoverage === 'require-all-mapped' && options.knownOperationIds) {
521
+ for (const operationId of options.knownOperationIds) {
522
+ if (!operationOwners.has(operationId)) {
523
+ issues.push({ code: 'unmapped_operation_id', path: 'scopes', message: `Unmapped V2 operationId: ${operationId}` });
524
+ }
525
+ }
526
+ }
527
+ return Object.freeze({ valid: issues.length === 0, issues: Object.freeze(issues) });
528
+ }
@@ -0,0 +1,2 @@
1
+ export { getOAuthScopeDefinition, getOAuthScopeForOperationId, getOAuthScopeGroup, matchOAuthScopeGroup, oauthCatalog, oauthScopeGroups, oauthScopes, validateOAuthCatalog, } from './catalog';
2
+ export type { KnownOAuthScopeName, OAuthApiScopeAction, OAuthCatalogDefinition, OAuthCatalogIssue, OAuthCatalogIssueCode, OAuthCatalogValidationOptions, OAuthCatalogValidationResult, OAuthScopeCategory, OAuthScopeDefinition, OAuthScopeGroupDefinition, OAuthScopeGroupMatch, OAuthScopeLifecycle, OAuthScopeName, OAuthScopeResource, } from './types';
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateOAuthCatalog = exports.oauthScopes = exports.oauthScopeGroups = exports.oauthCatalog = exports.matchOAuthScopeGroup = exports.getOAuthScopeGroup = exports.getOAuthScopeForOperationId = exports.getOAuthScopeDefinition = void 0;
4
+ var catalog_1 = require("./catalog");
5
+ Object.defineProperty(exports, "getOAuthScopeDefinition", { enumerable: true, get: function () { return catalog_1.getOAuthScopeDefinition; } });
6
+ Object.defineProperty(exports, "getOAuthScopeForOperationId", { enumerable: true, get: function () { return catalog_1.getOAuthScopeForOperationId; } });
7
+ Object.defineProperty(exports, "getOAuthScopeGroup", { enumerable: true, get: function () { return catalog_1.getOAuthScopeGroup; } });
8
+ Object.defineProperty(exports, "matchOAuthScopeGroup", { enumerable: true, get: function () { return catalog_1.matchOAuthScopeGroup; } });
9
+ Object.defineProperty(exports, "oauthCatalog", { enumerable: true, get: function () { return catalog_1.oauthCatalog; } });
10
+ Object.defineProperty(exports, "oauthScopeGroups", { enumerable: true, get: function () { return catalog_1.oauthScopeGroups; } });
11
+ Object.defineProperty(exports, "oauthScopes", { enumerable: true, get: function () { return catalog_1.oauthScopes; } });
12
+ Object.defineProperty(exports, "validateOAuthCatalog", { enumerable: true, get: function () { return catalog_1.validateOAuthCatalog; } });
@@ -0,0 +1,50 @@
1
+ export type OAuthScopeCategory = 'identity' | 'offline' | 'api';
2
+ export type OAuthScopeLifecycle = 'active' | 'deprecated' | 'retired';
3
+ export type OAuthApiScopeAction = 'read' | 'create' | 'update' | 'delete';
4
+ export type OAuthScopeResource = 'me' | 'summary' | 'categories' | 'crypto_manual' | 'crypto_synced' | 'balance_history' | 'manual_accounts' | 'plaid_accounts' | 'transactions' | 'transaction_attachments' | 'tags' | 'recurring_items' | 'budgets';
5
+ /**
6
+ * Durable scope names currently recognized by this catalog version.
7
+ * Persisted and API-facing scope values remain strings so newer names can be
8
+ * preserved and reported as unknown by consumers with an older catalog.
9
+ */
10
+ export type KnownOAuthScopeName = 'openid' | 'email' | 'profile' | 'offline_access' | 'me:read' | 'me:update' | 'summary:read' | 'categories:read' | 'categories:create' | 'categories:update' | 'categories:delete' | 'crypto_manual:read' | 'crypto_manual:create' | 'crypto_manual:update' | 'crypto_manual:delete' | 'crypto_synced:read' | 'crypto_synced:update' | 'balance_history:read' | 'balance_history:update' | 'balance_history:delete' | 'manual_accounts:read' | 'manual_accounts:create' | 'manual_accounts:update' | 'manual_accounts:delete' | 'plaid_accounts:read' | 'plaid_accounts:update' | 'transactions:read' | 'transactions:create' | 'transactions:update' | 'transactions:delete' | 'transaction_attachments:read' | 'transaction_attachments:create' | 'transaction_attachments:delete' | 'tags:read' | 'tags:create' | 'tags:update' | 'tags:delete' | 'recurring_items:read' | 'budgets:read' | 'budgets:update' | 'budgets:delete';
11
+ export type OAuthScopeName = string;
12
+ export interface OAuthScopeDefinition {
13
+ readonly name: KnownOAuthScopeName;
14
+ readonly description: string;
15
+ readonly category: OAuthScopeCategory;
16
+ readonly operationIds: readonly string[];
17
+ readonly status: OAuthScopeLifecycle;
18
+ readonly lifecycleNotice?: string;
19
+ }
20
+ export interface OAuthScopeGroupDefinition {
21
+ readonly id: string;
22
+ readonly name: string;
23
+ readonly description: string;
24
+ readonly scopes: readonly KnownOAuthScopeName[];
25
+ }
26
+ export interface OAuthCatalogDefinition {
27
+ readonly scopes: readonly OAuthScopeDefinition[];
28
+ readonly groups: readonly OAuthScopeGroupDefinition[];
29
+ }
30
+ export type OAuthCatalogIssueCode = 'invalid_scope_name' | 'invalid_group_id' | 'missing_description' | 'duplicate_scope_name' | 'duplicate_group_id' | 'duplicate_group_members' | 'duplicate_group_scope' | 'empty_group' | 'invalid_category' | 'invalid_lifecycle' | 'missing_operation_ids' | 'unexpected_operation_ids' | 'duplicate_operation_id' | 'conflicting_operation_id' | 'unknown_group_scope' | 'retired_group_scope' | 'offline_scope_in_group' | 'unknown_operation_id' | 'unmapped_operation_id';
31
+ export interface OAuthCatalogIssue {
32
+ readonly code: OAuthCatalogIssueCode;
33
+ readonly path: string;
34
+ readonly message: string;
35
+ }
36
+ export interface OAuthCatalogValidationOptions {
37
+ readonly knownOperationIds?: ReadonlySet<string>;
38
+ readonly operationIdCoverage?: 'allow-unmapped' | 'require-all-mapped';
39
+ }
40
+ export interface OAuthCatalogValidationResult {
41
+ readonly valid: boolean;
42
+ readonly issues: readonly OAuthCatalogIssue[];
43
+ }
44
+ export type OAuthScopeGroupMatch = {
45
+ readonly status: 'matched';
46
+ readonly group: OAuthScopeGroupDefinition;
47
+ } | {
48
+ readonly status: 'no_match';
49
+ readonly reason: 'duplicate_scope' | 'unknown_scope' | 'no_exact_match';
50
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,16 +1,31 @@
1
1
  {
2
2
  "name": "@lunch-money/v2-api-spec",
3
- "version": "2.11.1-preview.5",
3
+ "version": "2.11.1-preview.9",
4
4
  "description": "OpenAPI specification and version history for the Lunch Money v2 API",
5
5
  "main": "lunch-money-api-v2.yaml",
6
+ "exports": {
7
+ ".": "./lunch-money-api-v2.yaml",
8
+ "./lunch-money-api-v2.yaml": "./lunch-money-api-v2.yaml",
9
+ "./version-history.md": "./version-history.md",
10
+ "./oauth-scopes": {
11
+ "types": "./oauth-scopes/index.d.ts",
12
+ "require": "./oauth-scopes/index.js",
13
+ "default": "./oauth-scopes/index.js"
14
+ },
15
+ "./package.json": "./package.json",
16
+ "./*": "./*"
17
+ },
6
18
  "files": [
7
19
  "lunch-money-api-v2.yaml",
8
20
  "version-history.md",
21
+ "oauth-scopes",
9
22
  "README.md"
10
23
  ],
11
24
  "scripts": {
12
25
  "prepare": "husky install",
13
- "prepublishOnly": "node scripts/prep-for-publish.js",
26
+ "build:oauth-scopes": "tsc -p tsconfig.oauth-scopes.json",
27
+ "test:oauth-scopes": "npm run build:oauth-scopes && node --test test/oauth-scopes/*.test.js",
28
+ "prepublishOnly": "npm run build:oauth-scopes && node scripts/prep-for-publish.js",
14
29
  "postpublish": "node scripts/restore-readme.js",
15
30
  "publish:docs": "node scripts/publish-docs.js --tag preview",
16
31
  "publish:spec": "node scripts/publish-spec.js --tag preview",
@@ -30,6 +45,8 @@
30
45
  "author": "",
31
46
  "license": "ISC",
32
47
  "devDependencies": {
33
- "husky": "^8.0.3"
48
+ "husky": "^8.0.3",
49
+ "typescript": "^5.9.0",
50
+ "yaml": "^2.8.0"
34
51
  }
35
52
  }
@@ -6,8 +6,13 @@ The Lunch Money API spec uses a modified version of SEMVER for its versioning me
6
6
  - The revision number represents the number of updates since the last endpoint was added. For example, each time changes are made to one of the existing three APIs as described above, the revision number will be bumped.
7
7
 
8
8
  ## v2.11.1 - TBD
9
+ - Add the OAuth scope and scope-group catalog mapped to V2 `operationId` values
9
10
  - Add `GET /me/account/settings` and `PUT /me/account/settings` for account-level settings
10
11
  - Add `GET /me/user/settings` and `PUT /me/user/settings` for user-level display and formatting preferences
12
+ - Add `GET /me/user/account/settings` and `PUT /me/user/account/settings` for settings specific to a user and budgeting account
13
+ from `GET /me/account/settings` and `PUT /me/account/settings` to `GET /me/user/account/settings` and `PUT /me/user/account/settings`
14
+ - Add `include_pending_in_totals` to `GET /me/account/settings` and `PUT /me/account/settings`
15
+ - Add `default_manual_account_id` to `GET /me/user/account/settings` and `PUT /me/user/account/settings`
11
16
  - Document `GET /budgets/settings` response schema publicly; change `budget_period_quantity` to integer
12
17
 
13
18
  ## v2.11.0 - Jul 31, 2026