@epilot/customer-portal-client 0.49.0 → 0.51.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.
package/src/openapi.json CHANGED
@@ -55,6 +55,7 @@
55
55
  "/v2/portal/portal": {
56
56
  "post": {
57
57
  "operationId": "upsertPortal",
58
+ "deprecated": true,
58
59
  "summary": "upsertPortal",
59
60
  "description": "Upserts the settings for a portal of an organization.",
60
61
  "security": [
@@ -135,6 +136,9 @@
135
136
  "/v2/portal/public/user": {
136
137
  "post": {
137
138
  "operationId": "createUser",
139
+ "x-epilot-agent": {
140
+ "expose": false
141
+ },
138
142
  "summary": "createUser",
139
143
  "description": "Registers a portal user",
140
144
  "deprecated": true,
@@ -199,6 +203,9 @@
199
203
  "/v3/portal/public/user": {
200
204
  "post": {
201
205
  "operationId": "createUserV3",
206
+ "x-epilot-agent": {
207
+ "expose": false
208
+ },
202
209
  "summary": "createUserV3",
203
210
  "description": "Registers a portal user.\nSupports two identification methods:\n1. Using portal_id\n2. Using domain\n",
204
211
  "security": [],
@@ -271,6 +278,10 @@
271
278
  "/v2/portal/token/validate": {
272
279
  "post": {
273
280
  "operationId": "validateToken",
281
+ "x-epilot-agent": {
282
+ "expose": false
283
+ },
284
+ "x-portal-preview-read": true,
274
285
  "summary": "validateToken",
275
286
  "description": "Validates Portal Token is valid. Pass the token via Authorization Header.",
276
287
  "tags": [
@@ -298,6 +309,10 @@
298
309
  "/v2/portal/token/revoke": {
299
310
  "post": {
300
311
  "operationId": "revokeToken",
312
+ "x-epilot-agent": {
313
+ "expose": false
314
+ },
315
+ "x-portal-preview-read": true,
301
316
  "summary": "revokeToken",
302
317
  "description": "Revokes all of the access tokens for the given Refresh Token.",
303
318
  "tags": [
@@ -398,6 +413,7 @@
398
413
  "/v2/portal/config": {
399
414
  "get": {
400
415
  "operationId": "getPortalConfig",
416
+ "deprecated": true,
401
417
  "summary": "getPortalConfig",
402
418
  "description": "Retrieves the portal configuration.",
403
419
  "tags": [
@@ -444,6 +460,7 @@
444
460
  },
445
461
  "delete": {
446
462
  "operationId": "deletePortal",
463
+ "deprecated": true,
447
464
  "summary": "deletePortal",
448
465
  "description": "Deletes the portal.",
449
466
  "tags": [
@@ -484,6 +501,7 @@
484
501
  "/v1/portal/exports": {
485
502
  "post": {
486
503
  "operationId": "createExport",
504
+ "x-portal-preview-read": true,
487
505
  "summary": "createExport",
488
506
  "description": "Request an asynchronous CSV export of the portal user's entities for one schema. Columns come from the request Returns a job id to poll.\n",
489
507
  "tags": [
@@ -667,6 +685,7 @@
667
685
  "/v2/portal/extensions": {
668
686
  "get": {
669
687
  "operationId": "getPortalExtensions",
688
+ "deprecated": true,
670
689
  "summary": "getPortalExtensions",
671
690
  "description": "Retrieves the installed portal extensions.",
672
691
  "tags": [
@@ -717,6 +736,7 @@
717
736
  "/v2/portal/public/extensions": {
718
737
  "get": {
719
738
  "operationId": "getPublicPortalExtensionDetails",
739
+ "deprecated": true,
720
740
  "summary": "getPublicPortalExtensionDetails",
721
741
  "description": "Get public extension details shown to end customers and configuring users.",
722
742
  "tags": [
@@ -1074,6 +1094,7 @@
1074
1094
  "/v2/portal/visualization:export": {
1075
1095
  "post": {
1076
1096
  "operationId": "prepareVisualizationExport",
1097
+ "x-portal-preview-read": true,
1077
1098
  "summary": "prepareVisualizationExport",
1078
1099
  "description": "Asks an installed App to prepare a downloadable export of a visualization (consumption chart, dynamic tariff chart, etc.). The export is produced by the third-party App via a configured portal extension hook of type `dataExport` — this endpoint does not generate the file itself, it forwards the request to the configured hook and returns the descriptor the App provides (typically a `download_url`).\n",
1079
1100
  "tags": [
@@ -1652,6 +1673,7 @@
1652
1673
  "/v2/portal/external-links": {
1653
1674
  "get": {
1654
1675
  "operationId": "getExternalLinks",
1676
+ "deprecated": true,
1655
1677
  "summary": "getExternalLinks",
1656
1678
  "description": "Retrieves the portal configuration external links.",
1657
1679
  "tags": [
@@ -1986,6 +2008,7 @@
1986
2008
  "/v2/portal/public/portal/config": {
1987
2009
  "get": {
1988
2010
  "operationId": "getPublicPortalConfig",
2011
+ "deprecated": true,
1989
2012
  "summary": "getPublicPortalConfig",
1990
2013
  "description": "Retrieves the public portal configuration.",
1991
2014
  "tags": [
@@ -2033,6 +2056,7 @@
2033
2056
  "/v2/portal/org/portal/config": {
2034
2057
  "get": {
2035
2058
  "operationId": "getOrgPortalConfig",
2059
+ "deprecated": true,
2036
2060
  "summary": "getOrgPortalConfig",
2037
2061
  "description": "Retrieves the portal configuration for the organization.",
2038
2062
  "tags": [
@@ -2314,6 +2338,7 @@
2314
2338
  "/v2/portal/email-templates": {
2315
2339
  "post": {
2316
2340
  "operationId": "upsertEmailTemplates",
2341
+ "deprecated": true,
2317
2342
  "summary": "upsertEmailTemplates",
2318
2343
  "description": "Upserts the email templates of a portal",
2319
2344
  "tags": [
@@ -2385,6 +2410,7 @@
2385
2410
  },
2386
2411
  "get": {
2387
2412
  "operationId": "getEmailTemplates",
2413
+ "deprecated": true,
2388
2414
  "summary": "getEmailTemplates",
2389
2415
  "description": "Retrieves the email templates of a portal",
2390
2416
  "tags": [
@@ -2433,7 +2459,7 @@
2433
2459
  "post": {
2434
2460
  "operationId": "migrateEmailTemplateReferences",
2435
2461
  "summary": "migrateEmailTemplateReferences",
2436
- "description": "Walk every email-template config row in the caller's org and re-point any\nfield on `email_templates` that currently references `source_template_id`\nat `destination_template_id`. Intended to be called from the email-template\nmigration flow when a duplicated template is refined.\n\nOnly v3-shaped rows are migrated (those carrying `portal_sk_v3`). Returns\nthe portal IDs that were rewritten and any whose update failed.\n",
2462
+ "description": "Walk every email-template config row in the caller's org and re-point any\nfield on `email_templates` that currently references `source_template_id`\nat `destination_template_id`. Intended to be called from the email-template\nmigration flow when a duplicated template is refined.\n\nRows are keyed by portal id (`portal_sk_v3`); a row without one is skipped.\nReturns the portal IDs that were rewritten and any whose update failed.\n",
2437
2463
  "tags": [
2438
2464
  "ECP Admin"
2439
2465
  ],
@@ -3586,6 +3612,7 @@
3586
3612
  "/v2/portal/validate/caa-records": {
3587
3613
  "post": {
3588
3614
  "operationId": "validateCaaRecords",
3615
+ "deprecated": true,
3589
3616
  "summary": "validateCaaRecords",
3590
3617
  "description": "Validates the CAA records of a portal",
3591
3618
  "tags": [
@@ -3763,7 +3790,7 @@
3763
3790
  "patch": {
3764
3791
  "operationId": "updateContact",
3765
3792
  "summary": "updateContact",
3766
- "description": "Updates the contact details.",
3793
+ "description": "Updates the portal user's contact. Only the attributes in `PortalContactUpdate` can be changed;\nother attributes are ignored. Returns 403 when the body has none of them.\n",
3767
3794
  "deprecated": true,
3768
3795
  "tags": [
3769
3796
  "ECP"
@@ -3777,7 +3804,7 @@
3777
3804
  "content": {
3778
3805
  "application/json": {
3779
3806
  "schema": {
3780
- "$ref": "#/components/schemas/Entity"
3807
+ "$ref": "#/components/schemas/PortalContactUpdate"
3781
3808
  }
3782
3809
  }
3783
3810
  }
@@ -3870,6 +3897,7 @@
3870
3897
  "/v2/portal/public/contact/exists": {
3871
3898
  "post": {
3872
3899
  "operationId": "checkContactExists",
3900
+ "deprecated": true,
3873
3901
  "summary": "checkContactExists",
3874
3902
  "description": "True if contact with given identifiers exists.",
3875
3903
  "security": [],
@@ -4234,11 +4262,11 @@
4234
4262
  }
4235
4263
  }
4236
4264
  },
4237
- "/v2/portal/user": {
4265
+ "/v2/portal/copilot/access": {
4238
4266
  "get": {
4239
- "operationId": "getPortalUser",
4240
- "summary": "getPortalUser",
4241
- "description": "Get the portal user details",
4267
+ "operationId": "getPortalCopilotAccess",
4268
+ "summary": "getPortalCopilotAccess",
4269
+ "description": "Whether the signed-in portal user may use the portal chat. False when the chat is off for the portal, when the user does not match its target visibility, or when targets cannot be matched.",
4242
4270
  "tags": [
4243
4271
  "ECP"
4244
4272
  ],
@@ -4249,14 +4277,17 @@
4249
4277
  ],
4250
4278
  "responses": {
4251
4279
  "200": {
4252
- "description": "Portal user returned successfully.",
4280
+ "description": "Portal chat access of the portal user.",
4253
4281
  "content": {
4254
4282
  "application/json": {
4255
4283
  "schema": {
4256
4284
  "type": "object",
4285
+ "required": [
4286
+ "allowed"
4287
+ ],
4257
4288
  "properties": {
4258
- "data": {
4259
- "$ref": "#/components/schemas/PortalUser"
4289
+ "allowed": {
4290
+ "type": "boolean"
4260
4291
  }
4261
4292
  }
4262
4293
  }
@@ -4270,11 +4301,13 @@
4270
4301
  "$ref": "#/components/responses/InternalServerError"
4271
4302
  }
4272
4303
  }
4273
- },
4274
- "patch": {
4275
- "operationId": "updatePortalUser",
4276
- "summary": "updatePortalUser",
4277
- "description": "Update the portal user details",
4304
+ }
4305
+ },
4306
+ "/v2/portal/user": {
4307
+ "get": {
4308
+ "operationId": "getPortalUser",
4309
+ "summary": "getPortalUser",
4310
+ "description": "Get the portal user details",
4278
4311
  "tags": [
4279
4312
  "ECP"
4280
4313
  ],
@@ -4283,18 +4316,9 @@
4283
4316
  "PortalAuth": []
4284
4317
  }
4285
4318
  ],
4286
- "requestBody": {
4287
- "content": {
4288
- "application/json": {
4289
- "schema": {
4290
- "$ref": "#/components/schemas/Entity"
4291
- }
4292
- }
4293
- }
4294
- },
4295
4319
  "responses": {
4296
4320
  "200": {
4297
- "description": "The portal user updated successfully.",
4321
+ "description": "Portal user returned successfully.",
4298
4322
  "content": {
4299
4323
  "application/json": {
4300
4324
  "schema": {
@@ -4361,6 +4385,9 @@
4361
4385
  "/v2/portal/user/update/email": {
4362
4386
  "put": {
4363
4387
  "operationId": "updatePortalUserEmail",
4388
+ "x-epilot-agent": {
4389
+ "expose": false
4390
+ },
4364
4391
  "summary": "updatePortalUserEmail",
4365
4392
  "description": "Update portal user email",
4366
4393
  "tags": [
@@ -4429,6 +4456,9 @@
4429
4456
  "/v2/portal/user/change/password": {
4430
4457
  "put": {
4431
4458
  "operationId": "changePortalUserPassword",
4459
+ "x-epilot-agent": {
4460
+ "expose": false
4461
+ },
4432
4462
  "summary": "changePortalUserPassword",
4433
4463
  "description": "Hand over a password change to the third-party system configured via the `changePassword` portal extension hook.\nOnly available when such a hook is configured for the portal; the built-in password change flow does not use this endpoint.\n",
4434
4464
  "tags": [
@@ -4487,45 +4517,80 @@
4487
4517
  }
4488
4518
  }
4489
4519
  },
4490
- "/v2/portal/user/resend/confirmation-email/{id}": {
4491
- "post": {
4492
- "operationId": "resendConfirmationEmail",
4493
- "summary": "resendConfirmationEmail",
4494
- "description": "Resend confirmation email",
4520
+ "/v2/portal/user/notification-preferences": {
4521
+ "get": {
4522
+ "operationId": "getPortalUserNotificationPreferences",
4523
+ "summary": "getPortalUserNotificationPreferences",
4524
+ "description": "Returns the notification preferences of the authenticated portal user. Notifications follow an opt-out model: a preference that was never stored on the portal user defaults to enabled (true). Responds 404 while the engagement-center v2 feature is not enabled for the organization.",
4495
4525
  "tags": [
4496
- "ECP Admin"
4526
+ "ECP"
4497
4527
  ],
4498
4528
  "security": [
4499
4529
  {
4500
- "EpilotAuth": []
4530
+ "PortalAuth": []
4501
4531
  }
4502
4532
  ],
4503
- "parameters": [
4533
+ "responses": {
4534
+ "200": {
4535
+ "description": "The resolved notification preferences of the authenticated portal user.",
4536
+ "content": {
4537
+ "application/json": {
4538
+ "schema": {
4539
+ "$ref": "#/components/schemas/PortalUserNotificationPreferences"
4540
+ }
4541
+ }
4542
+ }
4543
+ },
4544
+ "401": {
4545
+ "$ref": "#/components/responses/Unauthorized"
4546
+ },
4547
+ "403": {
4548
+ "$ref": "#/components/responses/Forbidden"
4549
+ },
4550
+ "404": {
4551
+ "$ref": "#/components/responses/NotFound"
4552
+ },
4553
+ "500": {
4554
+ "$ref": "#/components/responses/InternalServerError"
4555
+ }
4556
+ }
4557
+ },
4558
+ "put": {
4559
+ "operationId": "updatePortalUserNotificationPreferences",
4560
+ "summary": "updatePortalUserNotificationPreferences",
4561
+ "description": "Partially updates the notification preferences of the authenticated portal user. Only the channels present in the request body are changed; omitted channels keep their stored value (or the enabled-by-default behaviour when never set). Returns the resulting full preferences. Responds 404 while the engagement-center v2 feature is not enabled for the organization.",
4562
+ "tags": [
4563
+ "ECP"
4564
+ ],
4565
+ "security": [
4504
4566
  {
4505
- "in": "path",
4506
- "name": "id",
4507
- "schema": {
4508
- "$ref": "#/components/schemas/EntityId"
4509
- },
4510
- "required": true,
4511
- "description": "The ID of portal user id"
4567
+ "PortalAuth": []
4512
4568
  }
4513
4569
  ],
4570
+ "requestBody": {
4571
+ "required": true,
4572
+ "content": {
4573
+ "application/json": {
4574
+ "schema": {
4575
+ "type": "object",
4576
+ "description": "Channels to update. Omitted channels are left unchanged.",
4577
+ "properties": {
4578
+ "in_app": {
4579
+ "type": "boolean",
4580
+ "description": "Whether the portal user wants to receive in-app notifications."
4581
+ }
4582
+ }
4583
+ }
4584
+ }
4585
+ }
4586
+ },
4514
4587
  "responses": {
4515
4588
  "200": {
4516
- "description": "Confirmation email resent successfully for the portal user.",
4589
+ "description": "The resulting full notification preferences after the update.",
4517
4590
  "content": {
4518
4591
  "application/json": {
4519
4592
  "schema": {
4520
- "type": "object",
4521
- "properties": {
4522
- "message": {
4523
- "type": "string",
4524
- "enum": [
4525
- "Confirmation email sent successfully."
4526
- ]
4527
- }
4528
- }
4593
+ "$ref": "#/components/schemas/PortalUserNotificationPreferences"
4529
4594
  }
4530
4595
  }
4531
4596
  }
@@ -4536,6 +4601,9 @@
4536
4601
  "401": {
4537
4602
  "$ref": "#/components/responses/Unauthorized"
4538
4603
  },
4604
+ "403": {
4605
+ "$ref": "#/components/responses/Forbidden"
4606
+ },
4539
4607
  "404": {
4540
4608
  "$ref": "#/components/responses/NotFound"
4541
4609
  },
@@ -4545,49 +4613,31 @@
4545
4613
  }
4546
4614
  }
4547
4615
  },
4548
- "/v2/portal/users/by-related-entity": {
4616
+ "/v2/portal/user/passkeys": {
4549
4617
  "get": {
4550
- "operationId": "fetchPortalUsersByRelatedEntity",
4551
- "summary": "fetchPortalUsersByRelatedEntity",
4552
- "description": "Get all users for a given entity",
4618
+ "operationId": "listPortalUserPasskeys",
4619
+ "summary": "listPortalUserPasskeys",
4620
+ "description": "Lists the passkeys the logged-in portal user has added.",
4553
4621
  "tags": [
4554
- "ECP Admin"
4622
+ "ECP"
4555
4623
  ],
4556
4624
  "security": [
4557
4625
  {
4558
- "EpilotAuth": []
4559
- }
4560
- ],
4561
- "parameters": [
4562
- {
4563
- "in": "query",
4564
- "name": "entity_id",
4565
- "required": true,
4566
- "schema": {
4567
- "$ref": "#/components/schemas/EntityId"
4568
- }
4569
- },
4570
- {
4571
- "in": "query",
4572
- "name": "slug",
4573
- "required": true,
4574
- "schema": {
4575
- "$ref": "#/components/schemas/EntitySlug"
4576
- }
4626
+ "PortalAuth": []
4577
4627
  }
4578
4628
  ],
4579
4629
  "responses": {
4580
4630
  "200": {
4581
- "description": "Returns the portal users under the given entity.",
4631
+ "description": "The user's passkeys",
4582
4632
  "content": {
4583
4633
  "application/json": {
4584
4634
  "schema": {
4585
4635
  "type": "object",
4586
4636
  "properties": {
4587
- "portalUsers": {
4637
+ "results": {
4588
4638
  "type": "array",
4589
4639
  "items": {
4590
- "$ref": "#/components/schemas/PortalUser"
4640
+ "$ref": "#/components/schemas/PortalUserPasskey"
4591
4641
  }
4592
4642
  }
4593
4643
  }
@@ -4605,66 +4655,63 @@
4605
4655
  "$ref": "#/components/responses/InternalServerError"
4606
4656
  }
4607
4657
  }
4608
- }
4609
- },
4610
- "/v2/portal/user/confirm": {
4611
- "get": {
4612
- "operationId": "confirmUser",
4613
- "summary": "confirmUser",
4614
- "description": "Confirm a portal user",
4658
+ },
4659
+ "post": {
4660
+ "operationId": "addPortalUserPasskey",
4661
+ "summary": "addPortalUserPasskey",
4662
+ "description": "Stores a new passkey for the logged-in portal user.\nSend the browser's `navigator.credentials.create()` result for options from `createPortalUserPasskeyRegistrationOptions`.\nOnly available when `auth_settings.passkeys.enabled` is set for the portal.\n",
4615
4663
  "tags": [
4616
- "Public"
4664
+ "ECP"
4617
4665
  ],
4618
- "security": [],
4619
- "parameters": [
4620
- {
4621
- "in": "query",
4622
- "name": "confirmation_link_token",
4623
- "required": true,
4624
- "schema": {
4625
- "type": "string",
4626
- "description": "Confirmation link token"
4627
- }
4628
- },
4666
+ "security": [
4629
4667
  {
4630
- "in": "query",
4631
- "name": "use_redirect",
4632
- "required": false,
4633
- "schema": {
4634
- "type": "boolean",
4635
- "description": "Should the operation result in a 301 redirect",
4636
- "default": true
4637
- }
4668
+ "PortalAuth": []
4638
4669
  }
4639
4670
  ],
4671
+ "requestBody": {
4672
+ "required": true,
4673
+ "content": {
4674
+ "application/json": {
4675
+ "schema": {
4676
+ "type": "object",
4677
+ "required": [
4678
+ "registration_response"
4679
+ ],
4680
+ "properties": {
4681
+ "registration_response": {
4682
+ "type": "object",
4683
+ "additionalProperties": true,
4684
+ "description": "WebAuthn RegistrationResponseJSON from the browser"
4685
+ },
4686
+ "name": {
4687
+ "type": "string",
4688
+ "maxLength": 64,
4689
+ "description": "Label shown to the user, e.g. \"iPhone\""
4690
+ }
4691
+ }
4692
+ }
4693
+ }
4694
+ }
4695
+ },
4640
4696
  "responses": {
4641
- "200": {
4642
- "description": "Returned in case of successful user confirmation",
4697
+ "201": {
4698
+ "description": "The passkey was added",
4643
4699
  "content": {
4644
4700
  "application/json": {
4645
4701
  "schema": {
4646
- "type": "object",
4647
- "properties": {
4648
- "confirmed": {
4649
- "type": "boolean",
4650
- "description": "Is the user confirmed",
4651
- "example": true
4652
- },
4653
- "user_already_confirmed": {
4654
- "type": "boolean",
4655
- "description": "Is the user previously confirmed",
4656
- "example": true
4657
- }
4658
- }
4702
+ "$ref": "#/components/schemas/PortalUserPasskey"
4659
4703
  }
4660
4704
  }
4661
4705
  }
4662
4706
  },
4663
- "301": {
4664
- "description": "User is confirmed and redirected to the portal successfully."
4665
- },
4666
4707
  "400": {
4667
- "$ref": "#/components/responses/ConfirmUserInvalidRequest"
4708
+ "$ref": "#/components/responses/InvalidRequest"
4709
+ },
4710
+ "401": {
4711
+ "$ref": "#/components/responses/Unauthorized"
4712
+ },
4713
+ "403": {
4714
+ "$ref": "#/components/responses/Forbidden"
4668
4715
  },
4669
4716
  "500": {
4670
4717
  "$ref": "#/components/responses/InternalServerError"
@@ -4672,31 +4719,346 @@
4672
4719
  }
4673
4720
  }
4674
4721
  },
4675
- "/v2/portal/user/confirm/{id}": {
4676
- "get": {
4677
- "operationId": "confirmUserWithUserId",
4678
- "summary": "confirmUserWithUserId",
4679
- "description": "Confirm a portal user",
4722
+ "/v2/portal/user/passkeys/registration-options": {
4723
+ "post": {
4724
+ "operationId": "createPortalUserPasskeyRegistrationOptions",
4725
+ "summary": "createPortalUserPasskeyRegistrationOptions",
4726
+ "description": "Returns WebAuthn options for `navigator.credentials.create()` to add a passkey for the logged-in portal user.\nNeeds the code from `sendPortalUserPasskeyVerificationCode`, so a session alone can't add a passkey; the code is used up here.\nThe passkey is bound to the domain in the request's `Origin` header, which must be one of the portal's domains.\nOnly available when `auth_settings.passkeys.enabled` is set for the portal.\n",
4680
4727
  "tags": [
4681
- "Public"
4728
+ "ECP"
4682
4729
  ],
4683
- "security": [],
4684
- "parameters": [
4685
- {
4686
- "in": "query",
4687
- "name": "origin",
4688
- "required": true,
4689
- "schema": {
4690
- "$ref": "#/components/schemas/Origin"
4691
- }
4692
- },
4730
+ "security": [
4693
4731
  {
4694
- "in": "path",
4695
- "name": "id",
4696
- "schema": {
4697
- "$ref": "#/components/schemas/EntityId"
4698
- },
4699
- "required": true,
4732
+ "PortalAuth": []
4733
+ }
4734
+ ],
4735
+ "requestBody": {
4736
+ "required": true,
4737
+ "content": {
4738
+ "application/json": {
4739
+ "schema": {
4740
+ "type": "object",
4741
+ "required": [
4742
+ "verification_code"
4743
+ ],
4744
+ "properties": {
4745
+ "verification_code": {
4746
+ "type": "string",
4747
+ "description": "The 6-digit code sent to the user's email address"
4748
+ }
4749
+ }
4750
+ }
4751
+ }
4752
+ }
4753
+ },
4754
+ "responses": {
4755
+ "200": {
4756
+ "description": "WebAuthn PublicKeyCredentialCreationOptionsJSON, valid for 5 minutes",
4757
+ "content": {
4758
+ "application/json": {
4759
+ "schema": {
4760
+ "type": "object",
4761
+ "additionalProperties": true
4762
+ }
4763
+ }
4764
+ }
4765
+ },
4766
+ "400": {
4767
+ "$ref": "#/components/responses/InvalidRequest"
4768
+ },
4769
+ "401": {
4770
+ "$ref": "#/components/responses/Unauthorized"
4771
+ },
4772
+ "403": {
4773
+ "$ref": "#/components/responses/Forbidden"
4774
+ },
4775
+ "500": {
4776
+ "$ref": "#/components/responses/InternalServerError"
4777
+ }
4778
+ }
4779
+ }
4780
+ },
4781
+ "/v2/portal/user/passkeys/verification-code": {
4782
+ "post": {
4783
+ "operationId": "sendPortalUserPasskeyVerificationCode",
4784
+ "summary": "sendPortalUserPasskeyVerificationCode",
4785
+ "description": "Emails the logged-in portal user a 6-digit code needed to add a passkey (`createPortalUserPasskeyRegistrationOptions`).\nThe code is valid for 10 minutes and allows 5 attempts; a new code replaces the previous one and can be requested once a minute.\nOnly available when `auth_settings.passkeys.enabled` is set for the portal.\n",
4786
+ "tags": [
4787
+ "ECP"
4788
+ ],
4789
+ "security": [
4790
+ {
4791
+ "PortalAuth": []
4792
+ }
4793
+ ],
4794
+ "responses": {
4795
+ "204": {
4796
+ "description": "The code was sent"
4797
+ },
4798
+ "401": {
4799
+ "$ref": "#/components/responses/Unauthorized"
4800
+ },
4801
+ "403": {
4802
+ "$ref": "#/components/responses/Forbidden"
4803
+ },
4804
+ "429": {
4805
+ "$ref": "#/components/responses/TooManyRequests"
4806
+ },
4807
+ "500": {
4808
+ "$ref": "#/components/responses/InternalServerError"
4809
+ }
4810
+ }
4811
+ }
4812
+ },
4813
+ "/v2/portal/user/passkeys/{credential_id}": {
4814
+ "delete": {
4815
+ "operationId": "deletePortalUserPasskey",
4816
+ "summary": "deletePortalUserPasskey",
4817
+ "description": "Removes one of the logged-in portal user's passkeys. Works even when passkeys are turned off for the portal.",
4818
+ "tags": [
4819
+ "ECP"
4820
+ ],
4821
+ "security": [
4822
+ {
4823
+ "PortalAuth": []
4824
+ }
4825
+ ],
4826
+ "parameters": [
4827
+ {
4828
+ "name": "credential_id",
4829
+ "in": "path",
4830
+ "required": true,
4831
+ "schema": {
4832
+ "type": "string"
4833
+ }
4834
+ }
4835
+ ],
4836
+ "responses": {
4837
+ "204": {
4838
+ "description": "The passkey was removed"
4839
+ },
4840
+ "401": {
4841
+ "$ref": "#/components/responses/Unauthorized"
4842
+ },
4843
+ "404": {
4844
+ "$ref": "#/components/responses/NotFound"
4845
+ },
4846
+ "500": {
4847
+ "$ref": "#/components/responses/InternalServerError"
4848
+ }
4849
+ }
4850
+ }
4851
+ },
4852
+ "/v2/portal/user/resend/confirmation-email/{id}": {
4853
+ "post": {
4854
+ "operationId": "resendConfirmationEmail",
4855
+ "summary": "resendConfirmationEmail",
4856
+ "description": "Resend confirmation email",
4857
+ "tags": [
4858
+ "ECP Admin"
4859
+ ],
4860
+ "security": [
4861
+ {
4862
+ "EpilotAuth": []
4863
+ }
4864
+ ],
4865
+ "parameters": [
4866
+ {
4867
+ "in": "path",
4868
+ "name": "id",
4869
+ "schema": {
4870
+ "$ref": "#/components/schemas/EntityId"
4871
+ },
4872
+ "required": true,
4873
+ "description": "The ID of portal user id"
4874
+ }
4875
+ ],
4876
+ "responses": {
4877
+ "200": {
4878
+ "description": "Confirmation email resent successfully for the portal user.",
4879
+ "content": {
4880
+ "application/json": {
4881
+ "schema": {
4882
+ "type": "object",
4883
+ "properties": {
4884
+ "message": {
4885
+ "type": "string",
4886
+ "enum": [
4887
+ "Confirmation email sent successfully."
4888
+ ]
4889
+ }
4890
+ }
4891
+ }
4892
+ }
4893
+ }
4894
+ },
4895
+ "400": {
4896
+ "$ref": "#/components/responses/InvalidRequest"
4897
+ },
4898
+ "401": {
4899
+ "$ref": "#/components/responses/Unauthorized"
4900
+ },
4901
+ "404": {
4902
+ "$ref": "#/components/responses/NotFound"
4903
+ },
4904
+ "500": {
4905
+ "$ref": "#/components/responses/InternalServerError"
4906
+ }
4907
+ }
4908
+ }
4909
+ },
4910
+ "/v2/portal/users/by-related-entity": {
4911
+ "get": {
4912
+ "operationId": "fetchPortalUsersByRelatedEntity",
4913
+ "summary": "fetchPortalUsersByRelatedEntity",
4914
+ "description": "Get all users for a given entity",
4915
+ "tags": [
4916
+ "ECP Admin"
4917
+ ],
4918
+ "security": [
4919
+ {
4920
+ "EpilotAuth": []
4921
+ }
4922
+ ],
4923
+ "parameters": [
4924
+ {
4925
+ "in": "query",
4926
+ "name": "entity_id",
4927
+ "required": true,
4928
+ "schema": {
4929
+ "$ref": "#/components/schemas/EntityId"
4930
+ }
4931
+ },
4932
+ {
4933
+ "in": "query",
4934
+ "name": "slug",
4935
+ "required": true,
4936
+ "schema": {
4937
+ "$ref": "#/components/schemas/EntitySlug"
4938
+ }
4939
+ }
4940
+ ],
4941
+ "responses": {
4942
+ "200": {
4943
+ "description": "Returns the portal users under the given entity.",
4944
+ "content": {
4945
+ "application/json": {
4946
+ "schema": {
4947
+ "type": "object",
4948
+ "properties": {
4949
+ "portalUsers": {
4950
+ "type": "array",
4951
+ "items": {
4952
+ "$ref": "#/components/schemas/PortalUser"
4953
+ }
4954
+ }
4955
+ }
4956
+ }
4957
+ }
4958
+ }
4959
+ },
4960
+ "401": {
4961
+ "$ref": "#/components/responses/Unauthorized"
4962
+ },
4963
+ "403": {
4964
+ "$ref": "#/components/responses/Forbidden"
4965
+ },
4966
+ "500": {
4967
+ "$ref": "#/components/responses/InternalServerError"
4968
+ }
4969
+ }
4970
+ }
4971
+ },
4972
+ "/v2/portal/user/confirm": {
4973
+ "get": {
4974
+ "operationId": "confirmUser",
4975
+ "summary": "confirmUser",
4976
+ "description": "Confirm a portal user",
4977
+ "tags": [
4978
+ "Public"
4979
+ ],
4980
+ "security": [],
4981
+ "parameters": [
4982
+ {
4983
+ "in": "query",
4984
+ "name": "confirmation_link_token",
4985
+ "required": true,
4986
+ "schema": {
4987
+ "type": "string",
4988
+ "description": "Confirmation link token"
4989
+ }
4990
+ },
4991
+ {
4992
+ "in": "query",
4993
+ "name": "use_redirect",
4994
+ "required": false,
4995
+ "schema": {
4996
+ "type": "boolean",
4997
+ "description": "Should the operation result in a 301 redirect",
4998
+ "default": true
4999
+ }
5000
+ }
5001
+ ],
5002
+ "responses": {
5003
+ "200": {
5004
+ "description": "Returned in case of successful user confirmation",
5005
+ "content": {
5006
+ "application/json": {
5007
+ "schema": {
5008
+ "type": "object",
5009
+ "properties": {
5010
+ "confirmed": {
5011
+ "type": "boolean",
5012
+ "description": "Is the user confirmed",
5013
+ "example": true
5014
+ },
5015
+ "user_already_confirmed": {
5016
+ "type": "boolean",
5017
+ "description": "Is the user previously confirmed",
5018
+ "example": true
5019
+ }
5020
+ }
5021
+ }
5022
+ }
5023
+ }
5024
+ },
5025
+ "301": {
5026
+ "description": "User is confirmed and redirected to the portal successfully."
5027
+ },
5028
+ "400": {
5029
+ "$ref": "#/components/responses/ConfirmUserInvalidRequest"
5030
+ },
5031
+ "500": {
5032
+ "$ref": "#/components/responses/InternalServerError"
5033
+ }
5034
+ }
5035
+ }
5036
+ },
5037
+ "/v2/portal/user/confirm/{id}": {
5038
+ "get": {
5039
+ "operationId": "confirmUserWithUserId",
5040
+ "summary": "confirmUserWithUserId",
5041
+ "description": "Confirm a portal user",
5042
+ "tags": [
5043
+ "Public"
5044
+ ],
5045
+ "security": [],
5046
+ "parameters": [
5047
+ {
5048
+ "in": "query",
5049
+ "name": "origin",
5050
+ "required": true,
5051
+ "schema": {
5052
+ "$ref": "#/components/schemas/Origin"
5053
+ }
5054
+ },
5055
+ {
5056
+ "in": "path",
5057
+ "name": "id",
5058
+ "schema": {
5059
+ "$ref": "#/components/schemas/EntityId"
5060
+ },
5061
+ "required": true,
4700
5062
  "description": "The ID of portal user id"
4701
5063
  },
4702
5064
  {
@@ -4723,6 +5085,7 @@
4723
5085
  "/v2/portal/public/user/exists": {
4724
5086
  "get": {
4725
5087
  "operationId": "userExists",
5088
+ "deprecated": true,
4726
5089
  "summary": "userExists",
4727
5090
  "description": "Checks whether a user exists in the portal",
4728
5091
  "tags": [
@@ -4961,6 +5324,7 @@
4961
5324
  "/v2/portal/configure-distribution": {
4962
5325
  "get": {
4963
5326
  "operationId": "configureDistribution",
5327
+ "deprecated": true,
4964
5328
  "summary": "configureDistribution",
4965
5329
  "description": "Configure the distribution for the portal's custom domain",
4966
5330
  "tags": [
@@ -5268,89 +5632,26 @@
5268
5632
  "items": {
5269
5633
  "$ref": "#/components/schemas/Product"
5270
5634
  }
5271
- },
5272
- "crossSellableProducts": {
5273
- "type": "array",
5274
- "description": "The related cross sellable products of the requested order",
5275
- "items": {
5276
- "$ref": "#/components/schemas/Product"
5277
- }
5278
- },
5279
- "workflow": {
5280
- "type": "array",
5281
- "description": "The related workflows of the requested order",
5282
- "items": {
5283
- "$ref": "#/components/schemas/WorkflowExecution"
5284
- }
5285
- },
5286
- "journey_actions": {
5287
- "type": "array",
5288
- "items": {
5289
- "$ref": "#/components/schemas/JourneyActions"
5290
- }
5291
- }
5292
- }
5293
- }
5294
- }
5295
- }
5296
- },
5297
- "401": {
5298
- "$ref": "#/components/responses/Unauthorized"
5299
- },
5300
- "403": {
5301
- "$ref": "#/components/responses/Forbidden"
5302
- },
5303
- "404": {
5304
- "$ref": "#/components/responses/NotFound"
5305
- },
5306
- "500": {
5307
- "$ref": "#/components/responses/InternalServerError"
5308
- }
5309
- }
5310
- },
5311
- "patch": {
5312
- "operationId": "updateOrder",
5313
- "summary": "updateOrder",
5314
- "description": "Update an order by id. Use getPortalUserEntity instead.",
5315
- "deprecated": true,
5316
- "tags": [
5317
- "ECP"
5318
- ],
5319
- "security": [
5320
- {
5321
- "PortalAuth": []
5322
- }
5323
- ],
5324
- "parameters": [
5325
- {
5326
- "in": "path",
5327
- "name": "id",
5328
- "schema": {
5329
- "$ref": "#/components/schemas/EntityId"
5330
- },
5331
- "required": true,
5332
- "description": "The ID of order"
5333
- }
5334
- ],
5335
- "requestBody": {
5336
- "content": {
5337
- "application/json": {
5338
- "schema": {
5339
- "$ref": "#/components/schemas/Entity"
5340
- }
5341
- }
5342
- }
5343
- },
5344
- "responses": {
5345
- "200": {
5346
- "description": "Updated the order details successfully.",
5347
- "content": {
5348
- "application/json": {
5349
- "schema": {
5350
- "type": "object",
5351
- "properties": {
5352
- "data": {
5353
- "$ref": "#/components/schemas/Order"
5635
+ },
5636
+ "crossSellableProducts": {
5637
+ "type": "array",
5638
+ "description": "The related cross sellable products of the requested order",
5639
+ "items": {
5640
+ "$ref": "#/components/schemas/Product"
5641
+ }
5642
+ },
5643
+ "workflow": {
5644
+ "type": "array",
5645
+ "description": "The related workflows of the requested order",
5646
+ "items": {
5647
+ "$ref": "#/components/schemas/WorkflowExecution"
5648
+ }
5649
+ },
5650
+ "journey_actions": {
5651
+ "type": "array",
5652
+ "items": {
5653
+ "$ref": "#/components/schemas/JourneyActions"
5654
+ }
5354
5655
  }
5355
5656
  }
5356
5657
  }
@@ -5531,6 +5832,7 @@
5531
5832
  "/v2/portal/opportunities/search": {
5532
5833
  "post": {
5533
5834
  "operationId": "getSearchResultsForOpportunities",
5835
+ "x-portal-preview-read": true,
5534
5836
  "summary": "getSearchResultsForOpportunities",
5535
5837
  "description": "Get all opportunity with the given searched attributes. Use searchPortalUserEntities instead.",
5536
5838
  "deprecated": true,
@@ -5726,71 +6028,6 @@
5726
6028
  "$ref": "#/components/responses/InternalServerError"
5727
6029
  }
5728
6030
  }
5729
- },
5730
- "patch": {
5731
- "operationId": "updateOpportunity",
5732
- "summary": "updateOpportunity",
5733
- "description": "Update an opportunity by id.",
5734
- "deprecated": true,
5735
- "tags": [
5736
- "ECP"
5737
- ],
5738
- "security": [
5739
- {
5740
- "PortalAuth": []
5741
- }
5742
- ],
5743
- "parameters": [
5744
- {
5745
- "in": "path",
5746
- "name": "id",
5747
- "schema": {
5748
- "$ref": "#/components/schemas/EntityId"
5749
- },
5750
- "required": true,
5751
- "description": "The ID of opportunity"
5752
- }
5753
- ],
5754
- "requestBody": {
5755
- "description": "Requested opportunity body to update",
5756
- "required": true,
5757
- "content": {
5758
- "application/json": {
5759
- "schema": {
5760
- "$ref": "#/components/schemas/Entity"
5761
- }
5762
- }
5763
- }
5764
- },
5765
- "responses": {
5766
- "200": {
5767
- "description": "Updated the opportunity successfully.",
5768
- "content": {
5769
- "application/json": {
5770
- "schema": {
5771
- "type": "object",
5772
- "properties": {
5773
- "data": {
5774
- "$ref": "#/components/schemas/Opportunity"
5775
- }
5776
- }
5777
- }
5778
- }
5779
- }
5780
- },
5781
- "401": {
5782
- "$ref": "#/components/responses/Unauthorized"
5783
- },
5784
- "403": {
5785
- "$ref": "#/components/responses/Forbidden"
5786
- },
5787
- "404": {
5788
- "$ref": "#/components/responses/NotFound"
5789
- },
5790
- "500": {
5791
- "$ref": "#/components/responses/InternalServerError"
5792
- }
5793
- }
5794
6031
  }
5795
6032
  },
5796
6033
  "/v2/portal/request": {
@@ -6069,7 +6306,7 @@
6069
6306
  "patch": {
6070
6307
  "operationId": "updateContract",
6071
6308
  "summary": "updateContract",
6072
- "description": "Update a contract by id. Use getPortalUserEntity instead.",
6309
+ "description": "Updates a contract of the portal user. Only the attributes in `PortalContractUpdate` can be changed;\nother attributes are ignored. Returns 403 when the body has none of them.\n",
6073
6310
  "deprecated": true,
6074
6311
  "tags": [
6075
6312
  "ECP"
@@ -6091,12 +6328,12 @@
6091
6328
  }
6092
6329
  ],
6093
6330
  "requestBody": {
6094
- "description": "Requested contract body to update",
6331
+ "description": "The contract attributes to change",
6095
6332
  "required": true,
6096
6333
  "content": {
6097
6334
  "application/json": {
6098
6335
  "schema": {
6099
- "$ref": "#/components/schemas/Entity"
6336
+ "$ref": "#/components/schemas/PortalContractUpdate"
6100
6337
  }
6101
6338
  }
6102
6339
  }
@@ -6135,6 +6372,7 @@
6135
6372
  "/v2/portal/contract/{id}/resolve-templates": {
6136
6373
  "post": {
6137
6374
  "operationId": "getContractWithTemplates",
6375
+ "x-portal-preview-read": true,
6138
6376
  "summary": "getContractWithTemplates",
6139
6377
  "description": "Resolve Handlebars templates against a contract's related meters and return the contract with templates_output populated per meter. This is a read-only operation — it does not modify the contract. It uses POST only so the templates can be passed in the request body. Prefer getPortalUserEntity / getContract for reads that don't need templates.",
6140
6378
  "tags": [
@@ -6756,71 +6994,12 @@
6756
6994
  "$ref": "#/components/responses/InternalServerError"
6757
6995
  }
6758
6996
  }
6759
- },
6760
- "delete": {
6761
- "operationId": "deleteEntityFile",
6762
- "summary": "deleteEntityFile",
6763
- "description": "Delete files from an entity",
6764
- "tags": [
6765
- "ECP"
6766
- ],
6767
- "security": [
6768
- {
6769
- "PortalAuth": []
6770
- }
6771
- ],
6772
- "requestBody": {
6773
- "description": "Delete file",
6774
- "required": true,
6775
- "content": {
6776
- "application/json": {
6777
- "schema": {
6778
- "$ref": "#/components/schemas/DeleteEntityFile"
6779
- }
6780
- }
6781
- }
6782
- },
6783
- "responses": {
6784
- "202": {
6785
- "description": "The files are removed from the requested entity successfully.",
6786
- "content": {
6787
- "application/json": {
6788
- "schema": {
6789
- "type": "object",
6790
- "properties": {
6791
- "deletedFiles": {
6792
- "type": "array",
6793
- "items": {
6794
- "$ref": "#/components/schemas/EntityId",
6795
- "description": "File ID"
6796
- }
6797
- }
6798
- }
6799
- }
6800
- }
6801
- }
6802
- },
6803
- "400": {
6804
- "$ref": "#/components/responses/InvalidRequest"
6805
- },
6806
- "401": {
6807
- "$ref": "#/components/responses/Unauthorized"
6808
- },
6809
- "403": {
6810
- "$ref": "#/components/responses/Forbidden"
6811
- },
6812
- "404": {
6813
- "$ref": "#/components/responses/NotFound"
6814
- },
6815
- "500": {
6816
- "$ref": "#/components/responses/InternalServerError"
6817
- }
6818
- }
6819
6997
  }
6820
6998
  },
6821
6999
  "/v2/portal/portal/files": {
6822
7000
  "post": {
6823
7001
  "operationId": "savePortalFiles",
7002
+ "deprecated": true,
6824
7003
  "summary": "savePortalFiles",
6825
7004
  "description": "Add files to portal",
6826
7005
  "tags": [
@@ -7477,8 +7656,11 @@
7477
7656
  "/v2/portal/admin:login-as-user": {
7478
7657
  "post": {
7479
7658
  "operationId": "loginToPortalAsUser",
7659
+ "x-epilot-agent": {
7660
+ "expose": false
7661
+ },
7480
7662
  "summary": "loginToPortalAsUser",
7481
- "description": "Generate a token to log in to a portal impersonating a users.\n\nToken is valid for 5 minutes.\n\nSupports three identification methods (at least one required):\n1. Using portal_id (takes precedence)\n2. Using domain\n3. Using origin (legacy)\n",
7663
+ "description": "Generate a token to log in to a portal impersonating a users.\n\nToken is valid for 5 minutes.\n\nSupports three identification methods (at least one required):\n1. Using portal_id (takes precedence)\n2. Using domain\n3. Using origin (legacy)\n\nA partner user can log in as a portal user the vendor shared with their organization by sending\n`x-epilot-org-id: <vendor org id>` with a `portal_id`. This needs `portal:login_as_user` on that portal in the\npartner role and the portal user shared with the partner organization.\n",
7482
7664
  "tags": [
7483
7665
  "ECP Admin"
7484
7666
  ],
@@ -7527,6 +7709,11 @@
7527
7709
  "login_as_token": {
7528
7710
  "type": "string",
7529
7711
  "description": "A generated login_as_token to log in to a portal impersonating a user."
7712
+ },
7713
+ "portal_url": {
7714
+ "type": "string",
7715
+ "description": "URL of the portal on its epilot domain, to open `/login-as?token=<login_as_token>` on.",
7716
+ "example": "https://my-portal.ecp.epilot.io"
7530
7717
  }
7531
7718
  }
7532
7719
  }
@@ -7539,6 +7726,7 @@
7539
7726
  "/v2/portal/entity/{schema}/access": {
7540
7727
  "post": {
7541
7728
  "operationId": "triggerEntityAccessEvent",
7729
+ "deprecated": true,
7542
7730
  "summary": "triggerEntityAccessEvent",
7543
7731
  "description": "Trigger entity access event for a portal user",
7544
7732
  "tags": [
@@ -7675,6 +7863,7 @@
7675
7863
  "/v2/portal/entity:get": {
7676
7864
  "post": {
7677
7865
  "operationId": "getPortalUserEntity",
7866
+ "x-portal-preview-read": true,
7678
7867
  "summary": "getPortalUserEntity",
7679
7868
  "description": "Get a single entity for a portal user",
7680
7869
  "tags": [
@@ -7724,6 +7913,7 @@
7724
7913
  "/v2/portal/entity:search": {
7725
7914
  "post": {
7726
7915
  "operationId": "searchPortalUserEntities",
7916
+ "x-portal-preview-read": true,
7727
7917
  "summary": "searchPortalUserEntities",
7728
7918
  "description": "Search all entities of a portal user",
7729
7919
  "x-contact-identification-token": true,
@@ -8175,7 +8365,7 @@
8175
8365
  "get": {
8176
8366
  "operationId": "getOutstandingTasks",
8177
8367
  "summary": "Get outstanding workflow tasks for the portal user",
8178
- "description": "Returns all outstanding workflow journey tasks for the authenticated portal user, across their opportunity, order and contract entities. Each task is an active workflow step that exposes a journey the customer still needs to fill out.",
8368
+ "description": "Returns all outstanding workflow journey tasks for the authenticated portal user, across their opportunity, order and contract entities. Each task is an active workflow step that exposes a journey the customer still needs to fill out. A step whose start date still lies in the future is left out until that date.",
8179
8369
  "tags": [
8180
8370
  "ECP"
8181
8371
  ],
@@ -8295,6 +8485,7 @@
8295
8485
  "/v2/portal/entities/workflows/linearized/batch": {
8296
8486
  "post": {
8297
8487
  "operationId": "getEntityPortalWorkflowsBatch",
8488
+ "x-portal-preview-read": true,
8298
8489
  "summary": "getEntityPortalWorkflowsBatch",
8299
8490
  "description": "Batch variant of `getEntityPortalWorkflows`: returns portal-relevant workflows for\nup to 50 entities — schemas may differ — in a single call, each already linearized\nby the Workflows API into a flat, ordered list of portal-visible tasks.\n\nIntended for portal list views (e.g. an entity list where every card shows its\nworkflow progress). Entities the portal user cannot access are omitted from the\nresponse; an entry with an empty task list means the entity has no portal-relevant\nworkflow.\n",
8300
8491
  "tags": [
@@ -8605,6 +8796,7 @@
8605
8796
  "/v2/portal/metering/readings": {
8606
8797
  "post": {
8607
8798
  "operationId": "getMeterReadings",
8799
+ "x-portal-preview-read": true,
8608
8800
  "summary": "getMeterReadings",
8609
8801
  "x-contact-identification-token": true,
8610
8802
  "description": "Fetches meter readings for a counter and optionally resolves Handlebars\ntemplate strings against each reading object using @epilot/variables.\n",
@@ -8665,6 +8857,11 @@
8665
8857
  },
8666
8858
  "description": "DEPRECATED — client-supplied template map resolved against the counter entity. Use `templates_ref` instead; same deprecation rules as `templates`."
8667
8859
  },
8860
+ "include_counter": {
8861
+ "type": "boolean",
8862
+ "default": false,
8863
+ "description": "When true, the response includes `counter` with the counter's unit, OBIS number, direction and tariff type. Access is checked by metering-api on the counter's parent meter."
8864
+ },
8668
8865
  "templates_ref": {
8669
8866
  "$ref": "#/components/schemas/TemplatesRef",
8670
8867
  "description": "Reference to the meter reading block whose admin-configured content provides both the per-reading templates (content_top_name, main_content_name, content_bottom_name) and the counter templates (counter_title_name, counter_subtitle_name), derived server-side."
@@ -8706,6 +8903,9 @@
8706
8903
  "type": "string"
8707
8904
  },
8708
8905
  "description": "Resolved counter templates."
8906
+ },
8907
+ "counter": {
8908
+ "$ref": "#/components/schemas/MeterReadingCounterDetails"
8709
8909
  }
8710
8910
  }
8711
8911
  }
@@ -8730,6 +8930,10 @@
8730
8930
  "/v2/portal/public/sso/providers/{provider_slug}": {
8731
8931
  "get": {
8732
8932
  "operationId": "getSSOProvider",
8933
+ "x-epilot-agent": {
8934
+ "expose": false
8935
+ },
8936
+ "deprecated": true,
8733
8937
  "summary": "getSSOProvider",
8734
8938
  "description": "Returns the public configuration of a single SSO identity provider with env var\nplaceholders (incl. secrets) already resolved against the organization's environment.\n\nUse this endpoint at SSO initiation time (i.e. when the end user clicks \"Sign in with X\")\nto obtain the resolved OIDC settings needed to construct the authorization URL.\nThe web `client_secret` is intentionally never returned — it is used server-side by\nthe SSO callback to exchange the authorization code for tokens.\n\nSupports three identification methods:\n1. `org_id` + `origin`\n2. `org_id` + `portal_id`\n3. `domain`\n",
8735
8939
  "security": [],
@@ -8814,6 +9018,9 @@
8814
9018
  "/v3/portal/public/sso/providers/{provider_slug}": {
8815
9019
  "get": {
8816
9020
  "operationId": "getPublicSSOProviderV3",
9021
+ "x-epilot-agent": {
9022
+ "expose": false
9023
+ },
8817
9024
  "summary": "getPublicSSOProviderV3",
8818
9025
  "description": "Returns the public configuration of a single SSO identity provider with env var\nplaceholders (incl. secrets) already resolved against the organization's environment.\n\nPortal-scoped variant of getSSOProvider: the portal is identified by `org_id` +\n`portal_id` only, so callers without a portal domain (e.g. standalone journeys)\ncan resolve the provider.\nOnly the web OIDC configuration is returned: `mobile_oidc_config` is omitted\nentirely, and the web `client_secret` and `metadata.test_auth_*` credentials are\nnever returned — they are used server-side by the SSO callback to exchange the\nauthorization code for tokens. `oidc_config.has_client_secret` is set when either\nexists, so clients route the exchange through the callback.\n",
8819
9026
  "security": [],
@@ -8876,6 +9083,10 @@
8876
9083
  "/v2/portal/public/sso/login": {
8877
9084
  "post": {
8878
9085
  "operationId": "ssoLogin",
9086
+ "x-epilot-agent": {
9087
+ "expose": false
9088
+ },
9089
+ "deprecated": true,
8879
9090
  "summary": "ssoLogin",
8880
9091
  "description": "Initiate login using external SSO identity.\n\nVerifies the user with the issuer and matches the identity to an epilot user (or creates a new user).\n\nReturns parameters to be used with CUSTOM_AUTH flow against Cognito\n",
8881
9092
  "security": [
@@ -8952,6 +9163,16 @@
8952
9163
  }
8953
9164
  }
8954
9165
  }
9166
+ },
9167
+ "400": {
9168
+ "description": "SSO login or token exchange failed",
9169
+ "content": {
9170
+ "application/json": {
9171
+ "schema": {
9172
+ "$ref": "#/components/schemas/SSOErrorResp"
9173
+ }
9174
+ }
9175
+ }
8955
9176
  }
8956
9177
  }
8957
9178
  }
@@ -8959,6 +9180,9 @@
8959
9180
  "/v3/portal/public/sso/login": {
8960
9181
  "post": {
8961
9182
  "operationId": "ssoLoginV3",
9183
+ "x-epilot-agent": {
9184
+ "expose": false
9185
+ },
8962
9186
  "summary": "ssoLoginV3",
8963
9187
  "description": "Initiate login using external SSO identity.\n\nVerifies the user with the issuer and matches the identity to an epilot user (or creates a new user).\n\nReturns parameters to be used with CUSTOM_AUTH flow against Cognito.\n\nSupports two identification methods:\n1. Using org_id + portal_id\n2. Using domain\n",
8964
9188
  "security": [
@@ -9051,7 +9275,14 @@
9051
9275
  }
9052
9276
  },
9053
9277
  "400": {
9054
- "$ref": "#/components/responses/InvalidRequest"
9278
+ "description": "SSO login or token exchange failed",
9279
+ "content": {
9280
+ "application/json": {
9281
+ "schema": {
9282
+ "$ref": "#/components/schemas/SSOErrorResp"
9283
+ }
9284
+ }
9285
+ }
9055
9286
  }
9056
9287
  }
9057
9288
  }
@@ -9059,6 +9290,9 @@
9059
9290
  "/v2/portal/public/sso/redirect": {
9060
9291
  "post": {
9061
9292
  "operationId": "ssoRedirect",
9293
+ "x-epilot-agent": {
9294
+ "expose": false
9295
+ },
9062
9296
  "summary": "ssoRedirect",
9063
9297
  "description": "Handles the redirect from the external SSO provider. Validates the authorization `code` and `state` received from the provider.\nRedirects the user to the provided `web_uri` with the validated credentials.\n",
9064
9298
  "security": [],
@@ -9123,6 +9357,9 @@
9123
9357
  "/v2/portal/public/sso/callback": {
9124
9358
  "post": {
9125
9359
  "operationId": "ssoCallback",
9360
+ "x-epilot-agent": {
9361
+ "expose": false
9362
+ },
9126
9363
  "summary": "ssoCallback",
9127
9364
  "description": "Handles the callback from the external SSO provider, validates the authorization `code`\nand generates a external provider token to be used with the CUSTOM_AUTH flow against Cognito.\n",
9128
9365
  "security": [],
@@ -9161,6 +9398,16 @@
9161
9398
  }
9162
9399
  }
9163
9400
  },
9401
+ "400": {
9402
+ "description": "SSO login or token exchange failed",
9403
+ "content": {
9404
+ "application/json": {
9405
+ "schema": {
9406
+ "$ref": "#/components/schemas/SSOErrorResp"
9407
+ }
9408
+ }
9409
+ }
9410
+ },
9164
9411
  "500": {
9165
9412
  "$ref": "#/components/responses/InternalServerError"
9166
9413
  }
@@ -9230,20 +9477,101 @@
9230
9477
  "EpilotAuth": []
9231
9478
  }
9232
9479
  ],
9233
- "requestBody": {
9234
- "description": "The portal page to update",
9235
- "required": true,
9236
- "content": {
9237
- "application/json": {
9238
- "schema": {
9239
- "$ref": "#/components/schemas/PageRequest"
9240
- }
9241
- }
9242
- }
9243
- },
9480
+ "requestBody": {
9481
+ "description": "The portal page to update",
9482
+ "required": true,
9483
+ "content": {
9484
+ "application/json": {
9485
+ "schema": {
9486
+ "$ref": "#/components/schemas/PageRequest"
9487
+ }
9488
+ }
9489
+ }
9490
+ },
9491
+ "responses": {
9492
+ "200": {
9493
+ "description": "The portal page has been fetched successfully.",
9494
+ "content": {
9495
+ "application/json": {
9496
+ "schema": {
9497
+ "$ref": "#/components/schemas/Page"
9498
+ }
9499
+ }
9500
+ }
9501
+ },
9502
+ "401": {
9503
+ "$ref": "#/components/responses/Unauthorized"
9504
+ },
9505
+ "403": {
9506
+ "$ref": "#/components/responses/Forbidden"
9507
+ },
9508
+ "404": {
9509
+ "$ref": "#/components/responses/NotFound"
9510
+ },
9511
+ "500": {
9512
+ "$ref": "#/components/responses/InternalServerError"
9513
+ }
9514
+ }
9515
+ },
9516
+ "delete": {
9517
+ "operationId": "deletePortalPage",
9518
+ "summary": "deletePortalPage",
9519
+ "description": "Delete a portal page by id",
9520
+ "tags": [
9521
+ "ECP Admin"
9522
+ ],
9523
+ "security": [
9524
+ {
9525
+ "EpilotAuth": []
9526
+ }
9527
+ ],
9528
+ "responses": {
9529
+ "204": {
9530
+ "description": "The portal page has been fetched successfully."
9531
+ },
9532
+ "401": {
9533
+ "$ref": "#/components/responses/Unauthorized"
9534
+ },
9535
+ "403": {
9536
+ "$ref": "#/components/responses/Forbidden"
9537
+ },
9538
+ "404": {
9539
+ "$ref": "#/components/responses/NotFound"
9540
+ },
9541
+ "500": {
9542
+ "$ref": "#/components/responses/InternalServerError"
9543
+ }
9544
+ }
9545
+ }
9546
+ },
9547
+ "/v2/portal/pages/{id}/detach": {
9548
+ "parameters": [
9549
+ {
9550
+ "in": "path",
9551
+ "name": "id",
9552
+ "required": true,
9553
+ "schema": {
9554
+ "type": "string",
9555
+ "format": "uuid",
9556
+ "example": "5da0a718-c822-403d-9f5d-20d4584e0528"
9557
+ }
9558
+ }
9559
+ ],
9560
+ "post": {
9561
+ "operationId": "detachPortalPage",
9562
+ "summary": "detachPortalPage",
9563
+ "description": "Detach a blueprint-managed page from its blueprint. The page and all of its blocks become local (editable, no longer updated by the blueprint) and the page's lineage is tombstoned on the portal so future syncs skip it. One-way. Idempotent for pages that are not managed.\n",
9564
+ "tags": [
9565
+ "ECP Admin"
9566
+ ],
9567
+ "security": [
9568
+ {
9569
+ "EpilotAuth": []
9570
+ }
9571
+ ],
9244
9572
  "responses": {
9245
9573
  "200": {
9246
- "description": "The portal page has been fetched successfully.",
9574
+ "description": "The detached page.",
9247
9575
  "content": {
9248
9576
  "application/json": {
9249
9577
  "schema": {
@@ -9265,11 +9593,35 @@
9265
9593
  "$ref": "#/components/responses/InternalServerError"
9266
9594
  }
9267
9595
  }
9268
- },
9269
- "delete": {
9270
- "operationId": "deletePortalPage",
9271
- "summary": "deletePortalPage",
9272
- "description": "Delete a portal page by id",
9596
+ }
9597
+ },
9598
+ "/v2/portal/pages/{id}/blocks/{block_id}/detach": {
9599
+ "parameters": [
9600
+ {
9601
+ "in": "path",
9602
+ "name": "id",
9603
+ "required": true,
9604
+ "schema": {
9605
+ "type": "string",
9606
+ "format": "uuid",
9607
+ "example": "5da0a718-c822-403d-9f5d-20d4584e0528"
9608
+ }
9609
+ },
9610
+ {
9611
+ "in": "path",
9612
+ "name": "block_id",
9613
+ "required": true,
9614
+ "schema": {
9615
+ "type": "string",
9616
+ "format": "uuid",
9617
+ "example": "c495fef9-eeca-4019-a989-8390dcd9825b"
9618
+ }
9619
+ }
9620
+ ],
9621
+ "post": {
9622
+ "operationId": "detachPortalPageBlock",
9623
+ "summary": "detachPortalPageBlock",
9624
+ "description": "Detach a blueprint-managed block. The block becomes the customer's and its lineage is tombstoned on the page so future syncs skip it. A managed page stays managed and keeps receiving blueprint updates for everything else. One-way. Idempotent for blocks that are not managed.\n",
9273
9625
  "tags": [
9274
9626
  "ECP Admin"
9275
9627
  ],
@@ -9279,8 +9631,15 @@
9279
9631
  }
9280
9632
  ],
9281
9633
  "responses": {
9282
- "204": {
9283
- "description": "The portal page has been fetched successfully."
9634
+ "200": {
9635
+ "description": "The detached block.",
9636
+ "content": {
9637
+ "application/json": {
9638
+ "schema": {
9639
+ "$ref": "#/components/schemas/Block"
9640
+ }
9641
+ }
9642
+ }
9284
9643
  },
9285
9644
  "401": {
9286
9645
  "$ref": "#/components/responses/Unauthorized"
@@ -9509,6 +9868,7 @@
9509
9868
  "/v2/portal/pages/interpolate": {
9510
9869
  "post": {
9511
9870
  "operationId": "interpolatePortalPages",
9871
+ "x-portal-preview-read": true,
9512
9872
  "summary": "interpolatePortalPages",
9513
9873
  "description": "Interpolate template variables in portal pages without reading from the database. Accepts pages in the request body and returns them with templates resolved. Portal Builder preview only: requires a 360 (epilot) token or a `portal_preview` token; plain portal user tokens get 403.",
9514
9874
  "tags": [
@@ -10508,6 +10868,9 @@
10508
10868
  "/v2/portal/public/m-login/deregister/{client_id}/{user_id}": {
10509
10869
  "delete": {
10510
10870
  "operationId": "deRegisterMLoginUser",
10871
+ "x-epilot-agent": {
10872
+ "expose": false
10873
+ },
10511
10874
  "summary": "deRegisterMLoginUser",
10512
10875
  "description": "Deregisters a user from the M Login client",
10513
10876
  "tags": [
@@ -10550,6 +10913,9 @@
10550
10913
  "/v2/portal/public/m-login/notify-interest-change/{client_id}/{user_id}": {
10551
10914
  "post": {
10552
10915
  "operationId": "notifyMLoginInterestChange",
10916
+ "x-epilot-agent": {
10917
+ "expose": false
10918
+ },
10553
10919
  "summary": "notifyMLoginInterestChange",
10554
10920
  "description": "Notifies the interest change of a user in the M Login client",
10555
10921
  "tags": [
@@ -10777,7 +11143,7 @@
10777
11143
  ],
10778
11144
  "default": "id"
10779
11145
  },
10780
- "description": "Determines how pages are matched for upsert operations:\n- `id` (default): Match pages by their ID. Use this when page IDs are stable and known upfront.\n- `slug`: Match pages by their slug. When a request page has the same slug as an existing page, the existing page ID is adopted. Use this when page ids are unknown or when source page IDs differ from destination page IDs.\n"
11146
+ "description": "Determines how pages are matched for upsert operations:\n- `id` (default): Match pages by their ID. Use this when page IDs are stable and known upfront.\n- `slug`: Match pages by their slug. When a request page has the same slug as an existing page, the existing page ID is adopted. Use this when page ids are unknown or when source page IDs differ from destination page IDs. A page may also bring its `_lineage_id` (the blueprint source page id); it is set once, on pages that have none.\n"
10781
11147
  }
10782
11148
  ],
10783
11149
  "requestBody": {
@@ -11153,6 +11519,71 @@
11153
11519
  }
11154
11520
  }
11155
11521
  },
11522
+ "/v3/portal/config/{portal_id}/revisions/{revision_id}:preview": {
11523
+ "post": {
11524
+ "operationId": "createPortalRevisionPreview",
11525
+ "summary": "createPortalRevisionPreview",
11526
+ "description": "Mints a short-lived preview token for one revision and returns a link that opens the revision inside the real end-customer portal, on the portal's live domain, without publishing it.\n\nThe portal sends the token back in the `x-portal-preview-token` header. Public and portal-user config, page, widget and extension reads that honour it answer with the revision's config and pages instead of the live ones and add `preview_revision` to config responses. Server-owned fields (ids, domains, Cognito pool identifiers, extension secrets) always come from the live portal. Nothing is written: the live portal, its pages and which revision is published stay as they are.\n\nRequires view permission on the portal. Returns `404` when portal versioning or revision preview is not enabled for the organization, or when the revision does not exist.\n",
11527
+ "tags": [
11528
+ "ECP Admin"
11529
+ ],
11530
+ "parameters": [
11531
+ {
11532
+ "in": "path",
11533
+ "name": "portal_id",
11534
+ "required": true,
11535
+ "schema": {
11536
+ "type": "string",
11537
+ "format": "uuid",
11538
+ "example": "5da0a718-c822-403d-9f5d-20d4584e0528"
11539
+ },
11540
+ "description": "Portal ID (readonly UUID generated on portal creation)"
11541
+ },
11542
+ {
11543
+ "in": "path",
11544
+ "name": "revision_id",
11545
+ "required": true,
11546
+ "schema": {
11547
+ "type": "string",
11548
+ "example": "2026-08-25T14:03:11.482Z-a7f3c1d9"
11549
+ },
11550
+ "description": "Revision ID. Contains `:` characters — percent-encode it."
11551
+ }
11552
+ ],
11553
+ "security": [
11554
+ {
11555
+ "EpilotAuth": []
11556
+ }
11557
+ ],
11558
+ "responses": {
11559
+ "200": {
11560
+ "description": "Preview token minted successfully.",
11561
+ "content": {
11562
+ "application/json": {
11563
+ "schema": {
11564
+ "$ref": "#/components/schemas/PortalRevisionPreview"
11565
+ }
11566
+ }
11567
+ }
11568
+ },
11569
+ "400": {
11570
+ "$ref": "#/components/responses/InvalidRequest"
11571
+ },
11572
+ "401": {
11573
+ "$ref": "#/components/responses/Unauthorized"
11574
+ },
11575
+ "403": {
11576
+ "$ref": "#/components/responses/Forbidden"
11577
+ },
11578
+ "404": {
11579
+ "$ref": "#/components/responses/NotFound"
11580
+ },
11581
+ "500": {
11582
+ "$ref": "#/components/responses/InternalServerError"
11583
+ }
11584
+ }
11585
+ }
11586
+ },
11156
11587
  "/v3/portal/config/{portal_id}/publish": {
11157
11588
  "post": {
11158
11589
  "operationId": "publishPortalRevision",
@@ -11410,6 +11841,16 @@
11410
11841
  "404": {
11411
11842
  "$ref": "#/components/responses/NotFound"
11412
11843
  },
11844
+ "409": {
11845
+ "description": "One of the portals (or one of their pages) is blueprint-managed; swaps are refused for them.",
11846
+ "content": {
11847
+ "application/json": {
11848
+ "schema": {
11849
+ "$ref": "#/components/schemas/ErrorResp"
11850
+ }
11851
+ }
11852
+ }
11853
+ },
11413
11854
  "500": {
11414
11855
  "$ref": "#/components/responses/InternalServerError"
11415
11856
  }
@@ -12399,6 +12840,72 @@
12399
12840
  }
12400
12841
  },
12401
12842
  "schemas": {
12843
+ "PortalUserPasskey": {
12844
+ "type": "object",
12845
+ "required": [
12846
+ "credential_id",
12847
+ "rp_id",
12848
+ "created_at"
12849
+ ],
12850
+ "properties": {
12851
+ "credential_id": {
12852
+ "type": "string",
12853
+ "description": "WebAuthn credential ID (base64url)"
12854
+ },
12855
+ "name": {
12856
+ "type": "string"
12857
+ },
12858
+ "rp_id": {
12859
+ "type": "string",
12860
+ "description": "Domain the passkey works on",
12861
+ "example": "kundenportal.example.de"
12862
+ },
12863
+ "device_type": {
12864
+ "type": "string",
12865
+ "enum": [
12866
+ "singleDevice",
12867
+ "multiDevice"
12868
+ ]
12869
+ },
12870
+ "backed_up": {
12871
+ "type": "boolean",
12872
+ "description": "Whether the passkey is synced, e.g. via iCloud Keychain or Google Password Manager"
12873
+ },
12874
+ "created_at": {
12875
+ "type": "string",
12876
+ "format": "date-time"
12877
+ },
12878
+ "last_used_at": {
12879
+ "type": "string",
12880
+ "format": "date-time"
12881
+ }
12882
+ }
12883
+ },
12884
+ "MeterReadingCounterDetails": {
12885
+ "type": "object",
12886
+ "description": "Display details of the counter the readings belong to. Only returned when `include_counter` is true and the counter could be resolved.",
12887
+ "properties": {
12888
+ "unit": {
12889
+ "type": "string",
12890
+ "nullable": true,
12891
+ "example": "kwh"
12892
+ },
12893
+ "obis_number": {
12894
+ "type": "string",
12895
+ "nullable": true,
12896
+ "example": "1-1:1.8.1"
12897
+ },
12898
+ "direction": {
12899
+ "type": "string",
12900
+ "nullable": true,
12901
+ "example": "feed-in"
12902
+ },
12903
+ "tariff_type": {
12904
+ "type": "string",
12905
+ "nullable": true
12906
+ }
12907
+ }
12908
+ },
12402
12909
  "PortalNotification": {
12403
12910
  "type": "object",
12404
12911
  "description": "A 360 notification addressed to a portal user.",
@@ -12462,6 +12969,20 @@
12462
12969
  }
12463
12970
  }
12464
12971
  },
12972
+ "PortalUserNotificationPreferences": {
12973
+ "type": "object",
12974
+ "description": "Resolved notification preferences of a portal user. Notifications are opt-out: a channel that was never stored on the portal user resolves to enabled (true).",
12975
+ "required": [
12976
+ "in_app"
12977
+ ],
12978
+ "properties": {
12979
+ "in_app": {
12980
+ "type": "boolean",
12981
+ "description": "Whether the portal user receives in-app notifications.",
12982
+ "example": true
12983
+ }
12984
+ }
12985
+ },
12465
12986
  "MobileBuildStatus": {
12466
12987
  "type": "object",
12467
12988
  "description": "Latest build/upload status for a platform (system-written).",
@@ -12821,6 +13342,92 @@
12821
13342
  }
12822
13343
  }
12823
13344
  },
13345
+ "SSOErrorResp": {
13346
+ "description": "Error response of the public SSO endpoints. Like every error response the message is in\n`error`; `reason` carries a stable machine-readable cause when one is known (e.g.\n`MISSING_EMAIL_CLAIM`, `INVALID_PROVIDER`, `SELF_REGISTRATION_NOT_ALLOWED`,\n`TOKEN_EXCHANGE_FAILED`). `debug` is only present while SSO debug mode is active for the\nresolved provider (`ProviderConfig.debug_mode_until`).\n",
13347
+ "type": "object",
13348
+ "properties": {
13349
+ "status": {
13350
+ "type": "integer",
13351
+ "example": 400
13352
+ },
13353
+ "error": {
13354
+ "type": "string",
13355
+ "description": "Error message"
13356
+ },
13357
+ "reason": {
13358
+ "type": "string",
13359
+ "description": "Machine-readable cause, when the API can name one."
13360
+ },
13361
+ "debug": {
13362
+ "$ref": "#/components/schemas/SSODebugInfo"
13363
+ }
13364
+ }
13365
+ },
13366
+ "SSODebugInfo": {
13367
+ "type": "object",
13368
+ "description": "Technical details of a failed SSO login or token exchange, returned only while SSO debug\nmode is active for the provider. Never carries secrets, authorization codes, PKCE\nverifiers, tokens or claim values.\n",
13369
+ "properties": {
13370
+ "stage": {
13371
+ "type": "string",
13372
+ "enum": [
13373
+ "exchange",
13374
+ "login"
13375
+ ],
13376
+ "description": "Which step failed — the backend token exchange (`ssoCallback`) or the login (`ssoLogin`)."
13377
+ },
13378
+ "reason": {
13379
+ "type": "string",
13380
+ "description": "Stable reason code, e.g. `TOKEN_EXCHANGE_FAILED`, `MISSING_EMAIL_CLAIM`, `INVALID_PROVIDER`.",
13381
+ "example": "TOKEN_EXCHANGE_FAILED"
13382
+ },
13383
+ "message": {
13384
+ "type": "string",
13385
+ "description": "The API's error message."
13386
+ },
13387
+ "upstream_status": {
13388
+ "type": "integer",
13389
+ "description": "HTTP status the IdP's token endpoint answered with.",
13390
+ "example": 401
13391
+ },
13392
+ "oauth_error": {
13393
+ "type": "string",
13394
+ "description": "The IdP's RFC 6749 §5.2 `error`.",
13395
+ "example": "invalid_client"
13396
+ },
13397
+ "oauth_error_description": {
13398
+ "type": "string",
13399
+ "description": "The IdP's RFC 6749 §5.2 `error_description` (e.g. the full `AADSTS…` text)."
13400
+ },
13401
+ "token_endpoint_host": {
13402
+ "type": "string",
13403
+ "example": "login.microsoftonline.com"
13404
+ },
13405
+ "token_endpoint_path": {
13406
+ "type": "string",
13407
+ "description": "Path of the configured token endpoint, without query.",
13408
+ "example": "/contoso.onmicrosoft.com/oauth2/v2.0/token"
13409
+ },
13410
+ "redirect_uri": {
13411
+ "type": "string",
13412
+ "description": "The redirect URI sent in the token exchange."
13413
+ },
13414
+ "received_claims": {
13415
+ "type": "array",
13416
+ "description": "Names (never values) of the claims received from the IdP.",
13417
+ "items": {
13418
+ "type": "string"
13419
+ }
13420
+ },
13421
+ "request_id": {
13422
+ "type": "string",
13423
+ "description": "API request id, for support."
13424
+ },
13425
+ "hint": {
13426
+ "type": "string",
13427
+ "description": "English hint for well-known IdP errors."
13428
+ }
13429
+ }
13430
+ },
12824
13431
  "FailedRuleErrorResp": {
12825
13432
  "type": "object",
12826
13433
  "properties": {
@@ -12917,33 +13524,6 @@
12917
13524
  }
12918
13525
  }
12919
13526
  },
12920
- "DeleteEntityFile": {
12921
- "type": "object",
12922
- "required": [
12923
- "file_entity_ids",
12924
- "entity_type",
12925
- "entity_id"
12926
- ],
12927
- "properties": {
12928
- "entity_id": {
12929
- "$ref": "#/components/schemas/EntityId"
12930
- },
12931
- "entity_type": {
12932
- "type": "string",
12933
- "example": "order",
12934
- "description": "Entity type"
12935
- },
12936
- "file_entity_ids": {
12937
- "type": "array",
12938
- "items": {
12939
- "$ref": "#/components/schemas/EntityId"
12940
- },
12941
- "minItems": 1,
12942
- "uniqueItems": true,
12943
- "description": "Array of file entity IDs"
12944
- }
12945
- }
12946
- },
12947
13527
  "SaveEntityFile": {
12948
13528
  "type": "object",
12949
13529
  "required": [
@@ -13352,7 +13932,18 @@
13352
13932
  "properties": {
13353
13933
  "enabled": {
13354
13934
  "type": "boolean",
13355
- "description": "Passwordless login feature flag"
13935
+ "description": "Passwordless login feature flag"
13936
+ }
13937
+ }
13938
+ },
13939
+ "passkeys": {
13940
+ "type": "object",
13941
+ "description": "Opt-in, set by the configuring user. When enabled, a second factor is enforced after the password and portal users can add passkeys (fingerprint, face or device PIN) as one variant of it, next to the email code of advanced_mfa. Users with a passkey use it by default; with advanced_mfa off, the passkey is the only second factor once a user has added one. On passwordless portals a passkey can replace the email code. Passkeys are bound to the domain they were added on. Default off; turning it off again restores the previous behaviour and keeps registered passkeys.\n",
13942
+ "properties": {
13943
+ "enabled": {
13944
+ "type": "boolean",
13945
+ "description": "Enforce a second factor and allow passkeys as one variant of it",
13946
+ "default": false
13356
13947
  }
13357
13948
  }
13358
13949
  },
@@ -13388,6 +13979,11 @@
13388
13979
  "example": "6bsd0jkgoie74k2i8mrhc1vest",
13389
13980
  "description": "Cognito user pool client ID"
13390
13981
  },
13982
+ "cognito_mobile_client_id": {
13983
+ "type": "string",
13984
+ "example": "3k9fj2l1m0n8b7v6c5x4z3a2s1",
13985
+ "description": "Cognito user pool client ID used by the native portal apps (longer refresh token validity)"
13986
+ },
13391
13987
  "cognito_user_pool_arn": {
13392
13988
  "type": "string",
13393
13989
  "example": "arn:aws:cognito-idp:us-east-1:123412341234:userpool/us-east-1_123412341",
@@ -13785,7 +14381,8 @@
13785
14381
  },
13786
14382
  "is_v3_item": {
13787
14383
  "type": "boolean",
13788
- "description": "Whether this is a v3 portal configuration"
14384
+ "deprecated": true,
14385
+ "description": "Always true. Legacy v2 storage was migrated; field kept for compatibility and will be removed."
13789
14386
  },
13790
14387
  "published_revision_id": {
13791
14388
  "type": "string",
@@ -13827,6 +14424,25 @@
13827
14424
  "engagement_center_enabled": {
13828
14425
  "type": "boolean",
13829
14426
  "description": "Master toggle for the portal's engagement center (in-app notifications including the notification triggers above). Off when absent — portal users only see the engagement center and receive trigger notifications after an admin enables it."
14427
+ },
14428
+ "engagement_center_task_templates": {
14429
+ "$ref": "#/components/schemas/EngagementCenterTaskTemplates"
14430
+ }
14431
+ }
14432
+ },
14433
+ "EngagementCenterTaskTemplates": {
14434
+ "type": "object",
14435
+ "description": "Handlebars templates rendering each outstanding task (to-do) row in the portal's engagement center. Resolved server-side per task against the task's entity (same variable syntax as page block templates, e.g. `{{order.order_number}}`, custom and environment variables) plus `task.*` (the workflow step, e.g. `{{task.name}}`), `workflow.*` (the execution, e.g. `{{workflow.name}}`) and `entity.*` (the task's entity regardless of its schema, e.g. `{{entity._title}}`). A template that is absent or blank keeps the portal's default rendering for that line. Admin/builder-only — never exposed via the public portal config.",
14436
+ "properties": {
14437
+ "title": {
14438
+ "type": "string",
14439
+ "description": "Template for the row title. Default rendering is the step name.",
14440
+ "example": "{{task.name}}"
14441
+ },
14442
+ "subtitle": {
14443
+ "type": "string",
14444
+ "description": "Template for the row's context line. Default rendering is the workflow name and the entity title.",
14445
+ "example": "{{workflow.name}} · {{entity._title}}"
13830
14446
  }
13831
14447
  }
13832
14448
  },
@@ -13838,8 +14454,9 @@
13838
14454
  "properties": {
13839
14455
  "trigger_type": {
13840
14456
  "type": "string",
13841
- "description": "The 360 event that fires this notification trigger.",
14457
+ "description": "The 360 event that fires this notification trigger. `entity_added` is the unified trigger written by the Portal Builder: it fires for both the entity-created and the entity-assigned event of its `entity_schema`. `entity_created` / `entity_assigned` are the legacy per-event triggers and keep working; when a portal has both a legacy row and an `entity_added` row for the same schema, the legacy row wins for its event.",
13842
14458
  "enum": [
14459
+ "entity_added",
13843
14460
  "entity_created",
13844
14461
  "entity_assigned",
13845
14462
  "workflow_step_overdue"
@@ -13847,7 +14464,7 @@
13847
14464
  },
13848
14465
  "entity_schema": {
13849
14466
  "type": "string",
13850
- "description": "For `entity_created` / `entity_assigned` triggers, the entity schema slug (e.g. `opportunity`, `order`) whose creation or assignment fires this trigger. Ignored for other trigger types.",
14467
+ "description": "For `entity_added` / `entity_created` / `entity_assigned` triggers, the entity schema slug (e.g. `opportunity`, `order`) whose creation or assignment fires this trigger. Ignored for other trigger types.",
13851
14468
  "example": "opportunity"
13852
14469
  },
13853
14470
  "enabled": {
@@ -13859,6 +14476,13 @@
13859
14476
  "type": "string",
13860
14477
  "format": "uuid",
13861
14478
  "description": "Entity id of the notification_template to render for this trigger."
14479
+ },
14480
+ "target_ids": {
14481
+ "type": "array",
14482
+ "description": "Targeting segment ids (targets) that narrow this trigger's recipients: the notification is only created for portal users whose contact matches at least one of the targets. Absent or empty means no narrowing — every portal user linked to the entity's contacts is notified.",
14483
+ "items": {
14484
+ "type": "string"
14485
+ }
13862
14486
  }
13863
14487
  }
13864
14488
  },
@@ -13890,6 +14514,34 @@
13890
14514
  "example": 12345,
13891
14515
  "description": "ID of the organization"
13892
14516
  },
14517
+ "_manifest": {
14518
+ "type": "array",
14519
+ "readOnly": true,
14520
+ "description": "Blueprint instance ids managing this portal. Server-managed: ignored (stripped) on every write from a user.\n",
14521
+ "items": {
14522
+ "type": "string"
14523
+ }
14524
+ },
14525
+ "_lineage_id": {
14526
+ "type": "string",
14527
+ "description": "Id of this portal in the blueprint source. Set once: a blueprint install or update (portal create, `page_upsert_mode=slug`, or a revision with `revision_source` `blueprint_sync`) may give a portal the lineage it does not have yet, unless another portal of the organization holds it. A portal created as a copy next to the organization's live portal of its origin (downgraded to `ADDITIONAL_PORTAL`) never takes it. After that it never changes; every other write keeps the stored value. Locks nothing on its own.\n"
14528
+ },
14529
+ "detached_lineage_ids": {
14530
+ "type": "array",
14531
+ "readOnly": true,
14532
+ "description": "Lineage ids of pages detached from their blueprint; a blueprint sync leaves them alone. Server-managed.\n",
14533
+ "items": {
14534
+ "type": "string"
14535
+ }
14536
+ },
14537
+ "held_back": {
14538
+ "type": "array",
14539
+ "readOnly": true,
14540
+ "description": "Only present on write responses: blueprint-managed pages/blocks the request tried to change. Their stored version was kept and the rest of the save succeeded.\n",
14541
+ "items": {
14542
+ "$ref": "#/components/schemas/HeldBackNode"
14543
+ }
14544
+ },
13893
14545
  "org_settings": {
13894
14546
  "type": "object",
13895
14547
  "description": "Organization settings",
@@ -13935,6 +14587,15 @@
13935
14587
  "items": {
13936
14588
  "$ref": "#/components/schemas/ProviderPublicConfig"
13937
14589
  }
14590
+ },
14591
+ "preview_revision": {
14592
+ "readOnly": true,
14593
+ "description": "Present only when the request carried a valid `x-portal-preview-token`: the config is this revision's, not the live one.\n",
14594
+ "allOf": [
14595
+ {
14596
+ "$ref": "#/components/schemas/PreviewRevision"
14597
+ }
14598
+ ]
13938
14599
  }
13939
14600
  }
13940
14601
  }
@@ -13952,7 +14613,8 @@
13952
14613
  },
13953
14614
  "is_v3_item": {
13954
14615
  "type": "boolean",
13955
- "description": "Indicates whether the widget configuration is stored as a V3 portal-scoped item"
14616
+ "deprecated": true,
14617
+ "description": "Always true. Legacy v2 storage was migrated; field kept for compatibility and will be removed."
13956
14618
  },
13957
14619
  "widgets": {
13958
14620
  "type": "array",
@@ -14409,6 +15071,11 @@
14409
15071
  "description": "Whether the identified contact already has a portal user on this portal, so the caller\ncan skip offering account creation. Present only on a match; omitted when it could not\nbe determined.\n",
14410
15072
  "example": false
14411
15073
  },
15074
+ "portal_user_email": {
15075
+ "type": "string",
15076
+ "description": "The e-mail the identified contact's portal user on this portal logs in with. It can\ndiffer from the contact's own e-mail. Present only when `has_portal_user` is true.\n",
15077
+ "example": "john.doe@example.com"
15078
+ },
14412
15079
  "reason": {
14413
15080
  "type": "string",
14414
15081
  "enum": [
@@ -15205,6 +15872,47 @@
15205
15872
  }
15206
15873
  ]
15207
15874
  },
15875
+ "PortalContactUpdate": {
15876
+ "type": "object",
15877
+ "description": "Contact attributes a portal user can change. Other attributes are ignored.\nValue types follow the organization's contact schema.\n",
15878
+ "additionalProperties": true,
15879
+ "properties": {
15880
+ "language": {
15881
+ "type": "string",
15882
+ "example": "de"
15883
+ },
15884
+ "first_name": {},
15885
+ "last_name": {},
15886
+ "salutation": {},
15887
+ "phone": {},
15888
+ "phone_number": {},
15889
+ "mobile": {},
15890
+ "street": {},
15891
+ "street_number": {},
15892
+ "postal_code": {},
15893
+ "city": {},
15894
+ "country": {},
15895
+ "preferred_language": {},
15896
+ "communication_preferences": {}
15897
+ }
15898
+ },
15899
+ "PortalContractUpdate": {
15900
+ "type": "object",
15901
+ "description": "Contract attributes a portal user can change. Other attributes are ignored.",
15902
+ "additionalProperties": true,
15903
+ "properties": {
15904
+ "installment_amount": {
15905
+ "type": "number",
15906
+ "description": "Installment amount in cents",
15907
+ "example": 8500
15908
+ },
15909
+ "installment_amount_decimal": {
15910
+ "type": "string",
15911
+ "description": "Installment amount as a decimal string",
15912
+ "example": "85.00"
15913
+ }
15914
+ }
15915
+ },
15208
15916
  "Contact": {
15209
15917
  "description": "The mapped contact of the portal user",
15210
15918
  "allOf": [
@@ -16209,6 +16917,39 @@
16209
16917
  "type": "string"
16210
16918
  }
16211
16919
  }
16920
+ },
16921
+ "link_hint": {
16922
+ "type": "object",
16923
+ "description": "Tells journey-api which route to link; omitted for a portal without an id",
16924
+ "required": [
16925
+ "route",
16926
+ "params"
16927
+ ],
16928
+ "properties": {
16929
+ "route": {
16930
+ "type": "string",
16931
+ "enum": [
16932
+ "portal-settings"
16933
+ ]
16934
+ },
16935
+ "params": {
16936
+ "type": "object",
16937
+ "required": [
16938
+ "portal_id"
16939
+ ],
16940
+ "properties": {
16941
+ "portal_id": {
16942
+ "type": "string"
16943
+ },
16944
+ "settings": {
16945
+ "type": "string",
16946
+ "enum": [
16947
+ "journey-actions"
16948
+ ]
16949
+ }
16950
+ }
16951
+ }
16952
+ }
16212
16953
  }
16213
16954
  }
16214
16955
  }
@@ -16339,6 +17080,14 @@
16339
17080
  "complete_task_automatically": {
16340
17081
  "type": "boolean",
16341
17082
  "description": "Whether submitting the journey from the portal should auto-complete the task. When false, an internal user completes it."
17083
+ },
17084
+ "title": {
17085
+ "type": "string",
17086
+ "description": "Row title rendered from the portal's `engagement_center_task_templates.title`. Absent when the portal has no title template or it rendered blank for this task; the client then shows `step_name`."
17087
+ },
17088
+ "subtitle": {
17089
+ "type": "string",
17090
+ "description": "Row context line rendered from the portal's `engagement_center_task_templates.subtitle`. Absent when the portal has no subtitle template or it rendered blank for this task; the client then shows the workflow name and entity title."
16342
17091
  }
16343
17092
  }
16344
17093
  },
@@ -16619,7 +17368,12 @@
16619
17368
  },
16620
17369
  "is_active": {
16621
17370
  "type": "boolean",
16622
- "description": "True when the task has been reached in the execution — all predecessor tasks on its\npath are completed/skipped — so the portal user can act on it (e.g. start its journey).\nTasks that are only part of the projected future path are returned with is_active false.\n"
17371
+ "description": "True when the task has been reached in the execution — all predecessor tasks on its\npath are completed/skipped — so the portal user can act on it (e.g. start its journey).\nTasks that are only part of the projected future path are returned with is_active false.\nThe start date is not taken into account: an active task can still be upcoming (see start_date).\n"
17372
+ },
17373
+ "start_date": {
17374
+ "type": "string",
17375
+ "format": "date-time",
17376
+ "description": "Instant before which nobody may work on the task. While it lies in the future, a task\nthat is not done yet is upcoming: the Workflows API refuses to complete it, so the\nportal shows it as upcoming instead of offering its journey. Neither status nor\nis_active reflects it; compare it with the current time. Absent when the task has no\nstart date or its relative start date has not been resolved yet.\n"
16623
17377
  },
16624
17378
  "ecp": {
16625
17379
  "$ref": "#/components/schemas/PortalTaskConfig"
@@ -17196,8 +17950,25 @@
17196
17950
  }
17197
17951
  },
17198
17952
  "sort": {
17199
- "type": "string",
17200
- "example": "_created_at:desc"
17953
+ "description": "Sort order of the results: an `attribute:order` pair, or a list of Elasticsearch sort fields that are\nused as they are, e.g. the sort order of a target as returned by the Targeting API.\nWithout a sort, the sort order of the first requested target that defines one applies.\n",
17954
+ "oneOf": [
17955
+ {
17956
+ "type": "string",
17957
+ "example": "_created_at:desc"
17958
+ },
17959
+ {
17960
+ "type": "array",
17961
+ "description": "At most 20 sort fields.",
17962
+ "items": {
17963
+ "type": "string",
17964
+ "pattern": "^[^\\s:,]+:(asc|desc)$"
17965
+ },
17966
+ "example": [
17967
+ "last_name.keyword:desc",
17968
+ "_id:desc"
17969
+ ]
17970
+ }
17971
+ ]
17201
17972
  },
17202
17973
  "from": {
17203
17974
  "type": "integer",
@@ -19589,6 +20360,13 @@
19589
20360
  "type": "boolean",
19590
20361
  "description": "Allow the resolved `client_secret` to be returned through the\npublic single-provider endpoint at SSO initiation. Only set this\nfor OIDC flows that require a public client secret in the\nbrowser (e.g. some PKCE-less authorization-code variants). When\nunset (default), the secret is kept server-side and only used at\nthe token exchange.\n",
19591
20362
  "example": false
20363
+ },
20364
+ "debug_mode_until": {
20365
+ "type": "string",
20366
+ "format": "date-time",
20367
+ "nullable": true,
20368
+ "description": "Temporarily enables SSO debug mode for this provider. While the current time is\nbefore this timestamp, failed SSO logins and token exchanges answer with a `debug`\nobject (`SSODebugInfo`) next to the error, so admins can see the real cause (e.g. the\nIdP's `AADSTS…` error) on the portal login page.\n\nCapped at 24 hours from the time of the write (plus 5 minutes clock-skew tolerance);\na later value is rejected with 400. `null` or absent turns debug mode off, and an\nexpired value is simply inactive. Never exposed publicly — public reads only carry\nthe derived `debug_mode` flag.\n",
20369
+ "example": "2026-10-30T12:00:00.000Z"
19592
20370
  }
19593
20371
  },
19594
20372
  "required": [
@@ -19610,6 +20388,11 @@
19610
20388
  },
19611
20389
  "mobile_oidc_config": {
19612
20390
  "$ref": "#/components/schemas/MoblieOIDCConfig"
20391
+ },
20392
+ "debug_mode": {
20393
+ "type": "boolean",
20394
+ "description": "Derived from `ProviderConfig.debug_mode_until`: `true` only while SSO debug mode is\nactive for this provider. Clients may use it to show technical error details on\nfailed SSO logins. Absent or `false` when debug mode is off.\n",
20395
+ "example": false
19613
20396
  }
19614
20397
  },
19615
20398
  "required": [
@@ -19629,6 +20412,11 @@
19629
20412
  },
19630
20413
  "oidc_config": {
19631
20414
  "$ref": "#/components/schemas/OIDCProviderConfig"
20415
+ },
20416
+ "debug_mode": {
20417
+ "type": "boolean",
20418
+ "description": "Derived from `ProviderConfig.debug_mode_until`: `true` only while SSO debug mode is\nactive for this provider. Clients may use it to show technical error details on\nfailed SSO logins. Absent or `false` when debug mode is off.\n",
20419
+ "example": false
19632
20420
  }
19633
20421
  },
19634
20422
  "required": [
@@ -20101,6 +20889,31 @@
20101
20889
  "description": "The type of the block. eg; tabs, tab, group, attribute",
20102
20890
  "example": "tab"
20103
20891
  },
20892
+ "HeldBackNode": {
20893
+ "type": "object",
20894
+ "description": "A blueprint-managed page or block a write tried to change; the stored version was kept.",
20895
+ "required": [
20896
+ "page_id",
20897
+ "reason"
20898
+ ],
20899
+ "properties": {
20900
+ "page_id": {
20901
+ "type": "string",
20902
+ "example": "5da0a718-c822-403d-9f5d-20d4584e0528"
20903
+ },
20904
+ "block_id": {
20905
+ "type": "string",
20906
+ "description": "Set when a single block (not the whole page) was held back",
20907
+ "example": "c495fef9-eeca-4019-a989-8390dcd9825b"
20908
+ },
20909
+ "reason": {
20910
+ "type": "string",
20911
+ "enum": [
20912
+ "BLUEPRINT_MANAGED"
20913
+ ]
20914
+ }
20915
+ }
20916
+ },
20104
20917
  "BlockRequest": {
20105
20918
  "type": "object",
20106
20919
  "additionalProperties": true,
@@ -20147,6 +20960,24 @@
20147
20960
  "description": "The id of the block",
20148
20961
  "format": "uuid",
20149
20962
  "example": "c495fef9-eeca-4019-a989-8390dcd9825b"
20963
+ },
20964
+ "_manifest": {
20965
+ "type": "array",
20966
+ "readOnly": true,
20967
+ "description": "Blueprint instance ids managing this block. Non-empty means the block is blueprint-managed and read-only for users until it is detached. Server-managed: ignored (stripped) on every write from a user.\n",
20968
+ "items": {
20969
+ "type": "string"
20970
+ }
20971
+ },
20972
+ "_lineage_id": {
20973
+ "type": "string",
20974
+ "readOnly": true,
20975
+ "description": "Stable id of this block in the blueprint source. Server-managed."
20976
+ },
20977
+ "_level": {
20978
+ "type": "string",
20979
+ "readOnly": true,
20980
+ "description": "Reserved for author-defined block levels (e.g. `dummy`, `strict`). Not interpreted yet. Server-managed: ignored (stripped) on every write from a user.\n"
20150
20981
  }
20151
20982
  }
20152
20983
  }
@@ -20334,6 +21165,26 @@
20334
21165
  "old-dashboard",
20335
21166
  "home"
20336
21167
  ]
21168
+ },
21169
+ "_manifest": {
21170
+ "type": "array",
21171
+ "readOnly": true,
21172
+ "description": "Blueprint instance ids managing this page. Non-empty means the page is blueprint-managed: its page-level fields and its blueprint-managed blocks are read-only for users (single-target writes answer 409 `BLUEPRINT_MANAGED`, bulk saves keep the stored version), while detached and customer-added blocks on it stay editable and the page keeps receiving blueprint updates. Server-managed: ignored (stripped) on every write from a user.\n",
21173
+ "items": {
21174
+ "type": "string"
21175
+ }
21176
+ },
21177
+ "_lineage_id": {
21178
+ "type": "string",
21179
+ "description": "Stable id of this page in the blueprint source. Set once: a blueprint install or update (`page_upsert_mode=slug`, portal create, or a revision with `revision_source` `blueprint_sync`) may give a page the lineage it does not have yet, unless another page of the portal holds it. After that it never changes; every other write keeps the stored value. Locks nothing on its own.\n"
21180
+ },
21181
+ "detached_lineage_ids": {
21182
+ "type": "array",
21183
+ "readOnly": true,
21184
+ "description": "Lineage ids of blocks on this page that were detached from their blueprint; a blueprint sync leaves them alone. Server-managed.\n",
21185
+ "items": {
21186
+ "type": "string"
21187
+ }
20337
21188
  }
20338
21189
  }
20339
21190
  }
@@ -20455,6 +21306,17 @@
20455
21306
  }
20456
21307
  }
20457
21308
  },
21309
+ "passkeys": {
21310
+ "type": "object",
21311
+ "description": "Opt-in, set by the configuring user. When enabled, a second factor is enforced after the password and portal users can add passkeys (fingerprint, face or device PIN) as one variant of it, next to the email code of advanced_mfa. Users with a passkey use it by default; with advanced_mfa off, the passkey is the only second factor once a user has added one. On passwordless portals a passkey can replace the email code. Passkeys are bound to the domain they were added on. Default off; turning it off again restores the previous behaviour and keeps registered passkeys.\n",
21312
+ "properties": {
21313
+ "enabled": {
21314
+ "type": "boolean",
21315
+ "description": "Enforce a second factor and allow passkeys as one variant of it",
21316
+ "default": false
21317
+ }
21318
+ }
21319
+ },
20458
21320
  "entry_point": {
20459
21321
  "type": "string",
20460
21322
  "enum": [
@@ -20487,6 +21349,11 @@
20487
21349
  "example": "6bsd0jkgoie74k2i8mrhc1vest",
20488
21350
  "description": "Cognito user pool client ID"
20489
21351
  },
21352
+ "cognito_mobile_client_id": {
21353
+ "type": "string",
21354
+ "example": "3k9fj2l1m0n8b7v6c5x4z3a2s1",
21355
+ "description": "Cognito user pool client ID used by the native portal apps (longer refresh token validity)"
21356
+ },
20490
21357
  "cognito_user_pool_arn": {
20491
21358
  "type": "string",
20492
21359
  "example": "arn:aws:cognito-idp:us-east-1:123412341234:userpool/us-east-1_123412341",
@@ -20884,7 +21751,8 @@
20884
21751
  },
20885
21752
  "is_v3_item": {
20886
21753
  "type": "boolean",
20887
- "description": "Whether this is a v3 portal configuration"
21754
+ "deprecated": true,
21755
+ "description": "Always true. Legacy v2 storage was migrated; field kept for compatibility and will be removed."
20888
21756
  },
20889
21757
  "published_revision_id": {
20890
21758
  "type": "string",
@@ -21142,6 +22010,45 @@
21142
22010
  "portal_copilot_agent_id": {
21143
22011
  "type": "string",
21144
22012
  "description": "ID of the Agent Builder portal agent this portal's copilot chat runs, set by Agent Builder. When unset, or when the agent no longer exists in the portal's organization, the chat runs the default portal copilot."
22013
+ },
22014
+ "portal_copilot_visibility": {
22015
+ "$ref": "#/components/schemas/PortalCopilotVisibility"
22016
+ }
22017
+ }
22018
+ },
22019
+ "PortalCopilotVisibility": {
22020
+ "type": "object",
22021
+ "description": "Limits portal chat to portal users matching targets, with the same rules as page and block visibility. Without targets every signed-in portal user can use the chat.",
22022
+ "properties": {
22023
+ "visible_for_targets": {
22024
+ "type": "array",
22025
+ "items": {
22026
+ "type": "string"
22027
+ },
22028
+ "description": "Target IDs that must match for the portal user to use the chat"
22029
+ },
22030
+ "visible_for_operator": {
22031
+ "type": "string",
22032
+ "enum": [
22033
+ "and",
22034
+ "or"
22035
+ ],
22036
+ "description": "Operator for visible_for_targets matching"
22037
+ },
22038
+ "hidden_for_targets": {
22039
+ "type": "array",
22040
+ "items": {
22041
+ "type": "string"
22042
+ },
22043
+ "description": "Target IDs that if matched deny the portal user the chat"
22044
+ },
22045
+ "hidden_for_operator": {
22046
+ "type": "string",
22047
+ "enum": [
22048
+ "and",
22049
+ "or"
22050
+ ],
22051
+ "description": "Operator for hidden_for_targets matching"
21145
22052
  }
21146
22053
  }
21147
22054
  },
@@ -21360,9 +22267,66 @@
21360
22267
  },
21361
22268
  "design": {
21362
22269
  "type": "object",
21363
- "additionalProperties": true,
21364
22270
  "nullable": true,
21365
- "description": "The portal design as returned by design-builder parse (theme=NEW), for the served document; absent when the design could not be read"
22271
+ "description": "Portal design background and accent for the served document. `null` when the portal has no design or it could not be read.\n",
22272
+ "required": [
22273
+ "background",
22274
+ "accent",
22275
+ "last_modified_at"
22276
+ ],
22277
+ "properties": {
22278
+ "background": {
22279
+ "type": "object",
22280
+ "nullable": true,
22281
+ "description": "Spark background as hex colours; `null` when the design sets neither",
22282
+ "required": [
22283
+ "light",
22284
+ "dark"
22285
+ ],
22286
+ "properties": {
22287
+ "light": {
22288
+ "type": "string",
22289
+ "nullable": true,
22290
+ "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$",
22291
+ "example": "#f5f5f5"
22292
+ },
22293
+ "dark": {
22294
+ "type": "string",
22295
+ "nullable": true,
22296
+ "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$",
22297
+ "example": "#101010"
22298
+ }
22299
+ }
22300
+ },
22301
+ "accent": {
22302
+ "type": "object",
22303
+ "nullable": true,
22304
+ "description": "Spark accent as hex colours, falling back to the classic palette primary; dark falls back to light. `null` when the design sets neither\n",
22305
+ "required": [
22306
+ "light",
22307
+ "dark"
22308
+ ],
22309
+ "properties": {
22310
+ "light": {
22311
+ "type": "string",
22312
+ "nullable": true,
22313
+ "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$",
22314
+ "example": "#005eb4"
22315
+ },
22316
+ "dark": {
22317
+ "type": "string",
22318
+ "nullable": true,
22319
+ "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$",
22320
+ "example": "#4a9eff"
22321
+ }
22322
+ }
22323
+ },
22324
+ "last_modified_at": {
22325
+ "type": "string",
22326
+ "nullable": true,
22327
+ "description": "When the design was last saved"
22328
+ }
22329
+ }
21366
22330
  }
21367
22331
  }
21368
22332
  },
@@ -21602,6 +22566,51 @@
21602
22566
  }
21603
22567
  ]
21604
22568
  },
22569
+ "PortalRevisionPreview": {
22570
+ "type": "object",
22571
+ "required": [
22572
+ "preview_token",
22573
+ "expires_at",
22574
+ "url"
22575
+ ],
22576
+ "properties": {
22577
+ "preview_token": {
22578
+ "type": "string",
22579
+ "description": "Short-lived token that makes portal reads return this revision. Send it in the `x-portal-preview-token` header.\n"
22580
+ },
22581
+ "expires_at": {
22582
+ "type": "string",
22583
+ "format": "date-time",
22584
+ "description": "When the preview token stops being honoured."
22585
+ },
22586
+ "url": {
22587
+ "type": "string",
22588
+ "description": "Link that opens the revision in the portal on its live domain.",
22589
+ "example": "https://portal.example.com/?preview_token=eyJhbGciOiJIUzI1NiIs..."
22590
+ }
22591
+ }
22592
+ },
22593
+ "PreviewRevision": {
22594
+ "type": "object",
22595
+ "description": "The revision a config response shows instead of the live portal config.",
22596
+ "required": [
22597
+ "revision_id",
22598
+ "created_at"
22599
+ ],
22600
+ "properties": {
22601
+ "revision_id": {
22602
+ "type": "string",
22603
+ "example": "2026-08-25T14:03:11.482Z-a7f3c1d9"
22604
+ },
22605
+ "name": {
22606
+ "type": "string"
22607
+ },
22608
+ "created_at": {
22609
+ "type": "string",
22610
+ "format": "date-time"
22611
+ }
22612
+ }
22613
+ },
21605
22614
  "PortalRevision": {
21606
22615
  "allOf": [
21607
22616
  {
@@ -21672,6 +22681,13 @@
21672
22681
  "description": "Stable, machine-readable codes for best-effort post-publish side effects that exhausted their retries. Publishing itself succeeded; these are not error messages and not meant for display as-is. Clients map each code to their own localized text.\nKnown codes: `allowed_entities_change` (portal entity grants and detail pages did not sync), `dns_records_clear` (the previous domain's DNS records were not cleared). New codes may be added without a client update, so treat an unrecognized code as a generic \"a follow-up step did not finish\" case rather than an error.\n",
21673
22682
  "items": {
21674
22683
  "type": "string"
22684
+ }
22685
+ },
22686
+ "held_back": {
22687
+ "type": "array",
22688
+ "description": "Only present when non-empty: blueprint-managed live pages/blocks this publish could not replace because the publisher is not the owning blueprint. Their live version stays as it is; the rest of the revision was published. Detach a node to make it publishable.\n",
22689
+ "items": {
22690
+ "$ref": "#/components/schemas/HeldBackNode"
21675
22691
  },
21676
22692
  "example": [
21677
22693
  "allowed_entities_change"