@canopy-io/node 0.7.1 → 0.7.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @canopy-io/node
2
2
 
3
+ ## 0.7.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 66415c2: The NestJS README's first `CanopyModule.forRoot` example now includes the required `verify` option, and `@canopy-io/node`'s types are regenerated from the published spec, adding the `branding.view` and `branding.manage` API key scopes. No behaviour changes.
8
+
3
9
  ## 0.7.1
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -37,7 +37,8 @@ import { Canopy, isCanopyError } from "@canopy-io/node";
37
37
 
38
38
  const canopy = new Canopy({ apiKey: process.env.CANOPY_API_KEY });
39
39
 
40
- // The call every integrator makes on every request.
40
+ // A fresh decision from the API. On the request path, prefer LocalAuthorizer
41
+ // (below), which answers in-process.
41
42
  const { allowed } = await canopy.permissions.evaluate({
42
43
  identity_id: identityId,
43
44
  permission: "documents.read",
@@ -102,9 +103,10 @@ try {
102
103
  }
103
104
  ```
104
105
 
105
- Hosted Login tokens carry an audience, so name your client. Direct API tokens
106
- carry none and need no option — and a token that has an `aud` is refused
107
- rather than ignored when you have not said which client you are:
106
+ Hosted Login tokens carry an audience, your `client_id`, so name your client.
107
+ Direct API tokens carry none and need no option. A token that has an `aud` is
108
+ refused rather than ignored when you have not said which client you are, so a
109
+ token minted for another client is never accepted by accident:
108
110
 
109
111
  ```ts
110
112
  new TokenVerifier({ audience: process.env.CANOPY_OAUTH_CLIENT_ID });
package/dist/index.d.cts CHANGED
@@ -550,7 +550,7 @@ interface paths {
550
550
  put?: never;
551
551
  /**
552
552
  * Set an identity's password (admin)
553
- * @description Sets an identity's password directly (server-to-server admin), primarily for migrating users off a legacy store without the user present. Reuses the argon2id + HaveIBeenPwned path, so a breached password returns `400`; the prior hash is recorded to password history, any active failed-login lockout is cleared, and the identity's active sessions are revoked so a rotated credential can't keep a stale session alive. Returns `204 No Content`; returns `404` when the identity has no membership in this Environment.
553
+ * @description Sets an identity's password directly (server-to-server admin), primarily for migrating users off a legacy store without the user present. Reuses the argon2id + HaveIBeenPwned path, so a breached password returns `400`; any active failed-login lockout is cleared, and the identity's active sessions are revoked so a rotated credential can't keep a stale session alive. Returns `204 No Content`; returns `404` when the identity has no membership in this Environment.
554
554
  */
555
555
  post: operations["ApiIdentitiesController_setIdentityPassword"];
556
556
  delete?: never;
@@ -588,7 +588,7 @@ interface paths {
588
588
  };
589
589
  /**
590
590
  * List an identity's active sessions (admin)
591
- * @description Lists an identity's active sessions (server-to-server admin): the rows a customer reads to confirm from its own side that a sign-out took, or to show a person where they are signed in. Each row is a session's current refresh token, so `id` changes on every refresh. Returns `404` when the identity has no membership in this Environment.
591
+ * @description Lists an identity's active sessions in the caller's Environment (server-to-server admin): the rows a customer reads to confirm from its own side that a sign-out took, or to show a person where they are signed in. Each row is a session's current refresh token, so `id` changes on every refresh. Sessions the identity holds in its other Environments are not listed. Returns `404` when the identity has no membership in this Environment.
592
592
  */
593
593
  get: operations["ApiIdentitiesController_listIdentitySessions"];
594
594
  put?: never;
@@ -611,7 +611,7 @@ interface paths {
611
611
  post?: never;
612
612
  /**
613
613
  * Revoke one of an identity's sessions (admin)
614
- * @description Revokes one of an identity's sessions (server-to-server admin) by marking its refresh token revoked, so the next refresh from that device returns `401` while every other session carries on. Audited as `identity.session.revoked`, the same row the person's own per-session sign-out writes. Returns `204 No Content`; returns `404` when the identity has no membership in this Environment, or when the session is not the identity's or is already revoked.
614
+ * @description Revokes one of an identity's sessions in the caller's Environment (server-to-server admin) by marking its refresh token revoked, so the next refresh from that device returns `401` while every other session carries on. Audited as `identity.session.revoked`, the same row the person's own per-session sign-out writes. Returns `204 No Content`; returns `404` when the identity has no membership in this Environment, or when the session is not the identity's, belongs to another Environment, or is already revoked.
615
615
  */
616
616
  delete: operations["ApiIdentitiesController_revokeIdentitySession"];
617
617
  options?: never;
@@ -630,7 +630,7 @@ interface paths {
630
630
  put?: never;
631
631
  /**
632
632
  * Revoke all of an identity's sessions (admin)
633
- * @description Revokes all of an identity's sessions (server-to-server admin) by marking every active refresh token revoked, so the next refresh on any device returns `401` and the user must sign in again. In-flight access tokens remain valid until their short TTL lapses. Idempotent — revoking when no tokens are active is a no-op. Audits twice: `identity.sessions.admin_revoked` for the identity's own security log, and `session.all_revoked`, the subscribable webhook event, so a revoke made here reaches subscribers exactly as one made from the Console does. Returns `204 No Content`; returns `404` when the identity has no membership in this Environment.
633
+ * @description Revokes all of an identity's sessions in the caller's Environment (server-to-server admin) by marking every active refresh token it holds there revoked, so the next refresh on any device returns `401` and the user must sign in again. Sessions in the identity's other Environments are left alone; the Console's account route is the one that signs a person out everywhere. In-flight access tokens remain valid until their short TTL lapses. Idempotent — revoking when no tokens are active is a no-op. Audits twice: `identity.sessions.admin_revoked` for the identity's own security log, and `session.all_revoked`, the subscribable webhook event, so a revoke made here reaches subscribers exactly as one made from the Console does. Returns `204 No Content`; returns `404` when the identity has no membership in this Environment.
634
634
  */
635
635
  post: operations["ApiIdentitiesController_revokeIdentitySessions"];
636
636
  delete?: never;
@@ -892,7 +892,7 @@ interface paths {
892
892
  };
893
893
  /**
894
894
  * List hierarchy (scoped tree)
895
- * @description Returns the caller's accessible hierarchy as a nested tree for the current Environment, each node annotated with `access_state`, `can_access`, and `can_manage`. `user` principals (dashboard) see the full tree; API-key principals see only the subtree their `access_mode`/scopes grant — `full_access` unlocks everything, while scoped keys see nodes reachable via `hierarchy.view`. The response also includes a `scope` block listing `accessible_node_ids`, `scope_root_node_ids`, and `entry_node_ids`.
895
+ * @description Returns the caller's accessible hierarchy as a nested tree for the current Environment, each node annotated with `access_state`, `can_access`, and `can_manage`. `user` principals (dashboard), which need the Console capability covering `hierarchy.view`, see the full tree; API-key principals see only the subtree their `access_mode`/scopes grant — `full_access` unlocks everything, while scoped keys see nodes reachable via `hierarchy.view`. The response also includes a `scope` block listing `accessible_node_ids`, `scope_root_node_ids`, and `entry_node_ids`.
896
896
  */
897
897
  get: operations["ApiNodesController_listNodes"];
898
898
  put?: never;
@@ -2362,7 +2362,7 @@ interface paths {
2362
2362
  put?: never;
2363
2363
  /**
2364
2364
  * Create a new API key
2365
- * @description Issues a new API key in the active Application, scoped to the current Environment. The full `cnpy_`-prefixed secret is returned in the `key` field of the response and is not recoverable afterward, only a masked `key_preview` and metadata persist; the one exception is a retry carrying the same `Idempotency-Key` within 24 hours, which replays this response. `access_mode: "scoped"` requires a non-empty `scopes` array and enforces it on every authorization check; `access_mode: "full_access"` forbids `scopes` and bypasses RBAC entirely within the Application. Pass `expires_at` (ISO 8601) for an expiring key or omit it for none. Supports `Idempotency-Key` for safe retries, emits an `api_key.created` event, and is limited to 20 requests a minute per caller. A scope the platform does not offer is refused with `400`.
2365
+ * @description Issues a new API key in the active Application, scoped to the current Environment. The full `cnpy_`-prefixed secret is returned in the `key` field of the response and is not recoverable afterward, only a masked `key_preview` and metadata persist; the one exception is a retry carrying the same `Idempotency-Key` within 24 hours, which replays this response. `access_mode: "scoped"` requires a non-empty `scopes` array and enforces it on every authorization check; `access_mode: "full_access"` forbids `scopes` and bypasses RBAC entirely within the Application. Pass `expires_at` (ISO 8601) for an expiring key or omit it for none. Supports `Idempotency-Key` for safe retries, emits an `api_key.created` event, and is limited to 20 requests a minute per caller. A scope the platform does not offer is refused with `400`. A scoped API key can create only scoped keys whose scopes it holds itself; asking for more is refused with `403`.
2366
2366
  */
2367
2367
  post: operations["ApiApiKeysController_create"];
2368
2368
  delete?: never;
@@ -3963,7 +3963,7 @@ interface components {
3963
3963
  /** Format: date-time */
3964
3964
  created_at: string;
3965
3965
  /**
3966
- * @description The issuer this client's tokens name, which a relying party configures: `https://<custom auth domain>` while the Environment has an active one, else the platform's. Its discovery document is at `<issuer>/.well-known/openid-configuration`.
3966
+ * @description The issuer this client's tokens name, which a relying party configures: `https://<custom auth domain>` while the Environment has an active one, else the Environment's own, `<platform origin>/environments/<environment_id>`. Its discovery document is at `<issuer>/.well-known/openid-configuration`.
3967
3967
  * @example https://auth.yourapp.com
3968
3968
  */
3969
3969
  issuer: string;
@@ -4000,7 +4000,7 @@ interface components {
4000
4000
  /** Format: date-time */
4001
4001
  created_at: string;
4002
4002
  /**
4003
- * @description The issuer this client's tokens name, which a relying party configures: `https://<custom auth domain>` while the Environment has an active one, else the platform's. Its discovery document is at `<issuer>/.well-known/openid-configuration`.
4003
+ * @description The issuer this client's tokens name, which a relying party configures: `https://<custom auth domain>` while the Environment has an active one, else the Environment's own, `<platform origin>/environments/<environment_id>`. Its discovery document is at `<issuer>/.well-known/openid-configuration`.
4004
4004
  * @example https://auth.yourapp.com
4005
4005
  */
4006
4006
  issuer: string;
@@ -4013,6 +4013,7 @@ interface components {
4013
4013
  };
4014
4014
  UpdateOAuthClientDto: {
4015
4015
  name?: string;
4016
+ /** @description Replaces the registered redirect URIs. At least one is required, as at creation: a client with none could never complete a sign-in. */
4016
4017
  redirect_uris?: string[];
4017
4018
  /** @description Where this client may send someone after RP-initiated logout. Matched exactly. An empty array removes every destination, which leaves logout working with nowhere to return to. */
4018
4019
  post_logout_redirect_uris?: string[];
@@ -4053,12 +4054,12 @@ interface components {
4053
4054
  /** @description API key description */
4054
4055
  description?: string;
4055
4056
  /**
4056
- * @description Required. `scoped` enforces the `scopes` array on every authorization check (deny if the requested permission isn't listed). `full_access` bypasses RBAC entirely within the key's Application — every permission is granted. Pick `scoped` whenever possible; `full_access` should be a deliberate choice (use cases: bootstrap automation, trusted backend services that legitimately need App-wide access). `scoped` requires a non-empty `scopes` array; `full_access` forbids `scopes`.
4057
+ * @description Required. `scoped` enforces the `scopes` array on every authorization check (deny if the requested permission isn't listed). `full_access` bypasses RBAC entirely within the key's Application — every permission is granted. Pick `scoped` whenever possible; `full_access` should be a deliberate choice (use cases: bootstrap automation, trusted backend services that legitimately need App-wide access). `scoped` requires a non-empty `scopes` array; `full_access` forbids `scopes`. A scoped API key can create only scoped keys whose scopes it holds itself.
4057
4058
  * @enum {string}
4058
4059
  */
4059
4060
  access_mode: "scoped" | "full_access";
4060
4061
  /** @description Permission scopes this key is authorized for, each one the platform offers (`GET .../api-keys/scopes`). Required and must be non-empty when `access_mode` is `scoped`. Must be omitted when `access_mode` is `full_access`. */
4061
- scopes?: ("hierarchy.view" | "hierarchy.manage" | "rbac.view_roles" | "rbac.manage_roles" | "rbac.view_assignments" | "rbac.manage_assignments" | "permissions.evaluate" | "identity.view" | "identity.manage" | "api_key.view" | "api_key.manage" | "webhook.view" | "webhook.manage" | "oauth_client.view" | "oauth_client.manage")[];
4062
+ scopes?: ("hierarchy.view" | "hierarchy.manage" | "rbac.view_roles" | "rbac.manage_roles" | "rbac.view_assignments" | "rbac.manage_assignments" | "permissions.evaluate" | "identity.view" | "identity.manage" | "api_key.view" | "api_key.manage" | "webhook.view" | "webhook.manage" | "oauth_client.view" | "oauth_client.manage" | "branding.view" | "branding.manage")[];
4062
4063
  /** @description Expiration date (ISO 8601). Omit for no expiration. */
4063
4064
  expires_at?: string;
4064
4065
  };
@@ -7682,7 +7683,7 @@ interface operations {
7682
7683
  "application/json": components["schemas"]["ErrorResponseDto"];
7683
7684
  };
7684
7685
  };
7685
- /** @description The identity is not found, or the session does not belong to it or is already revoked */
7686
+ /** @description The identity is not found, or the session does not belong to it, belongs to another Environment, or is already revoked */
7686
7687
  404: {
7687
7688
  headers: {
7688
7689
  [name: string]: unknown;
@@ -7693,7 +7694,7 @@ interface operations {
7693
7694
  * "error": {
7694
7695
  * "statusCode": 404,
7695
7696
  * "code": null,
7696
- * "message": "The identity is not found, or the session does not belong to it or is already revoked",
7697
+ * "message": "The identity is not found, or the session does not belong to it, belongs to another Environment, or is already revoked",
7697
7698
  * "timestamp": "2026-04-20T12:00:00.000Z",
7698
7699
  * "path": "/api/v1/identities/{id}/sessions/{sessionId}",
7699
7700
  * "method": "DELETE"
@@ -13246,6 +13247,27 @@ interface operations {
13246
13247
  "application/json": components["schemas"]["ErrorResponseDto"];
13247
13248
  };
13248
13249
  };
13250
+ /** @description The caller is an organization administrator and the member also belongs to another organization in the Environment; the second factor is held once per identity, so only the application's administrators may reset it (`organization.member_mfa_shared`) */
13251
+ 409: {
13252
+ headers: {
13253
+ [name: string]: unknown;
13254
+ };
13255
+ content: {
13256
+ /**
13257
+ * @example {
13258
+ * "error": {
13259
+ * "statusCode": 409,
13260
+ * "code": null,
13261
+ * "message": "The caller is an organization administrator and the member also belongs to another organization in the Environment; the second factor is held once per identity, so only the application's administrators may reset it (`organization.member_mfa_shared`)",
13262
+ * "timestamp": "2026-04-20T12:00:00.000Z",
13263
+ * "path": "/api/v1/organizations/{id}/members/{identityId}/mfa/reset",
13264
+ * "method": "POST"
13265
+ * }
13266
+ * }
13267
+ */
13268
+ "application/json": components["schemas"]["ErrorResponseDto"];
13269
+ };
13270
+ };
13249
13271
  };
13250
13272
  };
13251
13273
  ApiOrganizationsController_listInvites: {
@@ -19166,7 +19188,7 @@ interface operations {
19166
19188
  "application/json": components["schemas"]["ErrorResponseDto"];
19167
19189
  };
19168
19190
  };
19169
- /** @description This token is not authorized for this endpoint (wrong principal type — e.g., admin token on identity-only endpoint, or vice versa) */
19191
+ /** @description The bearer token is not an admin or platform token, or the caller lacks the permission this operation checks: a scoped API key without that scope, an admin without the Account capability that covers it, or, for a node-scoped check, a node outside this Environment */
19170
19192
  403: {
19171
19193
  headers: {
19172
19194
  [name: string]: unknown;
@@ -19177,7 +19199,7 @@ interface operations {
19177
19199
  * "error": {
19178
19200
  * "statusCode": 403,
19179
19201
  * "code": null,
19180
- * "message": "This token is not authorized for this endpoint (wrong principal type — e.g., admin token on identity-only endpoint, or vice versa)",
19202
+ * "message": "The bearer token is not an admin or platform token, or the caller lacks the permission this operation checks: a scoped API key without that scope, an admin without the Account capability that covers it, or, for a node-scoped check, a node outside this Environment",
19181
19203
  * "timestamp": "2026-04-20T12:00:00.000Z",
19182
19204
  * "path": "/api/v1/branding",
19183
19205
  * "method": "GET"
@@ -19255,7 +19277,7 @@ interface operations {
19255
19277
  "application/json": components["schemas"]["ErrorResponseDto"];
19256
19278
  };
19257
19279
  };
19258
- /** @description This token is not authorized for this endpoint (wrong principal type — e.g., admin token on identity-only endpoint, or vice versa) */
19280
+ /** @description The bearer token is not an admin or platform token, or the caller lacks the permission this operation checks: a scoped API key without that scope, an admin without the Account capability that covers it, or, for a node-scoped check, a node outside this Environment */
19259
19281
  403: {
19260
19282
  headers: {
19261
19283
  [name: string]: unknown;
@@ -19266,7 +19288,7 @@ interface operations {
19266
19288
  * "error": {
19267
19289
  * "statusCode": 403,
19268
19290
  * "code": null,
19269
- * "message": "This token is not authorized for this endpoint (wrong principal type — e.g., admin token on identity-only endpoint, or vice versa)",
19291
+ * "message": "The bearer token is not an admin or platform token, or the caller lacks the permission this operation checks: a scoped API key without that scope, an admin without the Account capability that covers it, or, for a node-scoped check, a node outside this Environment",
19270
19292
  * "timestamp": "2026-04-20T12:00:00.000Z",
19271
19293
  * "path": "/api/v1/branding",
19272
19294
  * "method": "GET"
@@ -20882,7 +20904,7 @@ interface operations {
20882
20904
  "application/json": components["schemas"]["ErrorResponseDto"];
20883
20905
  };
20884
20906
  };
20885
- /** @description The bearer token is not an admin or platform token, or the caller lacks the permission this operation checks: a scoped API key without that scope, an admin without the Account capability that covers it, or, for a node-scoped check, a node outside this Environment */
20907
+ /** @description The caller lacks `api_key.manage` (a scoped API key without that scope, or an admin without the Account capability that covers it), or is a scoped API key asking for more than it holds: `full_access`, or a scope outside its own (`api_key.access_exceeds_caller`) */
20886
20908
  403: {
20887
20909
  headers: {
20888
20910
  [name: string]: unknown;
@@ -20893,10 +20915,10 @@ interface operations {
20893
20915
  * "error": {
20894
20916
  * "statusCode": 403,
20895
20917
  * "code": null,
20896
- * "message": "The bearer token is not an admin or platform token, or the caller lacks the permission this operation checks: a scoped API key without that scope, an admin without the Account capability that covers it, or, for a node-scoped check, a node outside this Environment",
20918
+ * "message": "The caller lacks `api_key.manage` (a scoped API key without that scope, or an admin without the Account capability that covers it), or is a scoped API key asking for more than it holds: `full_access`, or a scope outside its own (`api_key.access_exceeds_caller`)",
20897
20919
  * "timestamp": "2026-04-20T12:00:00.000Z",
20898
20920
  * "path": "/api/v1/api-keys",
20899
- * "method": "GET"
20921
+ * "method": "POST"
20900
20922
  * }
20901
20923
  * }
20902
20924
  */
package/dist/index.d.ts CHANGED
@@ -550,7 +550,7 @@ interface paths {
550
550
  put?: never;
551
551
  /**
552
552
  * Set an identity's password (admin)
553
- * @description Sets an identity's password directly (server-to-server admin), primarily for migrating users off a legacy store without the user present. Reuses the argon2id + HaveIBeenPwned path, so a breached password returns `400`; the prior hash is recorded to password history, any active failed-login lockout is cleared, and the identity's active sessions are revoked so a rotated credential can't keep a stale session alive. Returns `204 No Content`; returns `404` when the identity has no membership in this Environment.
553
+ * @description Sets an identity's password directly (server-to-server admin), primarily for migrating users off a legacy store without the user present. Reuses the argon2id + HaveIBeenPwned path, so a breached password returns `400`; any active failed-login lockout is cleared, and the identity's active sessions are revoked so a rotated credential can't keep a stale session alive. Returns `204 No Content`; returns `404` when the identity has no membership in this Environment.
554
554
  */
555
555
  post: operations["ApiIdentitiesController_setIdentityPassword"];
556
556
  delete?: never;
@@ -588,7 +588,7 @@ interface paths {
588
588
  };
589
589
  /**
590
590
  * List an identity's active sessions (admin)
591
- * @description Lists an identity's active sessions (server-to-server admin): the rows a customer reads to confirm from its own side that a sign-out took, or to show a person where they are signed in. Each row is a session's current refresh token, so `id` changes on every refresh. Returns `404` when the identity has no membership in this Environment.
591
+ * @description Lists an identity's active sessions in the caller's Environment (server-to-server admin): the rows a customer reads to confirm from its own side that a sign-out took, or to show a person where they are signed in. Each row is a session's current refresh token, so `id` changes on every refresh. Sessions the identity holds in its other Environments are not listed. Returns `404` when the identity has no membership in this Environment.
592
592
  */
593
593
  get: operations["ApiIdentitiesController_listIdentitySessions"];
594
594
  put?: never;
@@ -611,7 +611,7 @@ interface paths {
611
611
  post?: never;
612
612
  /**
613
613
  * Revoke one of an identity's sessions (admin)
614
- * @description Revokes one of an identity's sessions (server-to-server admin) by marking its refresh token revoked, so the next refresh from that device returns `401` while every other session carries on. Audited as `identity.session.revoked`, the same row the person's own per-session sign-out writes. Returns `204 No Content`; returns `404` when the identity has no membership in this Environment, or when the session is not the identity's or is already revoked.
614
+ * @description Revokes one of an identity's sessions in the caller's Environment (server-to-server admin) by marking its refresh token revoked, so the next refresh from that device returns `401` while every other session carries on. Audited as `identity.session.revoked`, the same row the person's own per-session sign-out writes. Returns `204 No Content`; returns `404` when the identity has no membership in this Environment, or when the session is not the identity's, belongs to another Environment, or is already revoked.
615
615
  */
616
616
  delete: operations["ApiIdentitiesController_revokeIdentitySession"];
617
617
  options?: never;
@@ -630,7 +630,7 @@ interface paths {
630
630
  put?: never;
631
631
  /**
632
632
  * Revoke all of an identity's sessions (admin)
633
- * @description Revokes all of an identity's sessions (server-to-server admin) by marking every active refresh token revoked, so the next refresh on any device returns `401` and the user must sign in again. In-flight access tokens remain valid until their short TTL lapses. Idempotent — revoking when no tokens are active is a no-op. Audits twice: `identity.sessions.admin_revoked` for the identity's own security log, and `session.all_revoked`, the subscribable webhook event, so a revoke made here reaches subscribers exactly as one made from the Console does. Returns `204 No Content`; returns `404` when the identity has no membership in this Environment.
633
+ * @description Revokes all of an identity's sessions in the caller's Environment (server-to-server admin) by marking every active refresh token it holds there revoked, so the next refresh on any device returns `401` and the user must sign in again. Sessions in the identity's other Environments are left alone; the Console's account route is the one that signs a person out everywhere. In-flight access tokens remain valid until their short TTL lapses. Idempotent — revoking when no tokens are active is a no-op. Audits twice: `identity.sessions.admin_revoked` for the identity's own security log, and `session.all_revoked`, the subscribable webhook event, so a revoke made here reaches subscribers exactly as one made from the Console does. Returns `204 No Content`; returns `404` when the identity has no membership in this Environment.
634
634
  */
635
635
  post: operations["ApiIdentitiesController_revokeIdentitySessions"];
636
636
  delete?: never;
@@ -892,7 +892,7 @@ interface paths {
892
892
  };
893
893
  /**
894
894
  * List hierarchy (scoped tree)
895
- * @description Returns the caller's accessible hierarchy as a nested tree for the current Environment, each node annotated with `access_state`, `can_access`, and `can_manage`. `user` principals (dashboard) see the full tree; API-key principals see only the subtree their `access_mode`/scopes grant — `full_access` unlocks everything, while scoped keys see nodes reachable via `hierarchy.view`. The response also includes a `scope` block listing `accessible_node_ids`, `scope_root_node_ids`, and `entry_node_ids`.
895
+ * @description Returns the caller's accessible hierarchy as a nested tree for the current Environment, each node annotated with `access_state`, `can_access`, and `can_manage`. `user` principals (dashboard), which need the Console capability covering `hierarchy.view`, see the full tree; API-key principals see only the subtree their `access_mode`/scopes grant — `full_access` unlocks everything, while scoped keys see nodes reachable via `hierarchy.view`. The response also includes a `scope` block listing `accessible_node_ids`, `scope_root_node_ids`, and `entry_node_ids`.
896
896
  */
897
897
  get: operations["ApiNodesController_listNodes"];
898
898
  put?: never;
@@ -2362,7 +2362,7 @@ interface paths {
2362
2362
  put?: never;
2363
2363
  /**
2364
2364
  * Create a new API key
2365
- * @description Issues a new API key in the active Application, scoped to the current Environment. The full `cnpy_`-prefixed secret is returned in the `key` field of the response and is not recoverable afterward, only a masked `key_preview` and metadata persist; the one exception is a retry carrying the same `Idempotency-Key` within 24 hours, which replays this response. `access_mode: "scoped"` requires a non-empty `scopes` array and enforces it on every authorization check; `access_mode: "full_access"` forbids `scopes` and bypasses RBAC entirely within the Application. Pass `expires_at` (ISO 8601) for an expiring key or omit it for none. Supports `Idempotency-Key` for safe retries, emits an `api_key.created` event, and is limited to 20 requests a minute per caller. A scope the platform does not offer is refused with `400`.
2365
+ * @description Issues a new API key in the active Application, scoped to the current Environment. The full `cnpy_`-prefixed secret is returned in the `key` field of the response and is not recoverable afterward, only a masked `key_preview` and metadata persist; the one exception is a retry carrying the same `Idempotency-Key` within 24 hours, which replays this response. `access_mode: "scoped"` requires a non-empty `scopes` array and enforces it on every authorization check; `access_mode: "full_access"` forbids `scopes` and bypasses RBAC entirely within the Application. Pass `expires_at` (ISO 8601) for an expiring key or omit it for none. Supports `Idempotency-Key` for safe retries, emits an `api_key.created` event, and is limited to 20 requests a minute per caller. A scope the platform does not offer is refused with `400`. A scoped API key can create only scoped keys whose scopes it holds itself; asking for more is refused with `403`.
2366
2366
  */
2367
2367
  post: operations["ApiApiKeysController_create"];
2368
2368
  delete?: never;
@@ -3963,7 +3963,7 @@ interface components {
3963
3963
  /** Format: date-time */
3964
3964
  created_at: string;
3965
3965
  /**
3966
- * @description The issuer this client's tokens name, which a relying party configures: `https://<custom auth domain>` while the Environment has an active one, else the platform's. Its discovery document is at `<issuer>/.well-known/openid-configuration`.
3966
+ * @description The issuer this client's tokens name, which a relying party configures: `https://<custom auth domain>` while the Environment has an active one, else the Environment's own, `<platform origin>/environments/<environment_id>`. Its discovery document is at `<issuer>/.well-known/openid-configuration`.
3967
3967
  * @example https://auth.yourapp.com
3968
3968
  */
3969
3969
  issuer: string;
@@ -4000,7 +4000,7 @@ interface components {
4000
4000
  /** Format: date-time */
4001
4001
  created_at: string;
4002
4002
  /**
4003
- * @description The issuer this client's tokens name, which a relying party configures: `https://<custom auth domain>` while the Environment has an active one, else the platform's. Its discovery document is at `<issuer>/.well-known/openid-configuration`.
4003
+ * @description The issuer this client's tokens name, which a relying party configures: `https://<custom auth domain>` while the Environment has an active one, else the Environment's own, `<platform origin>/environments/<environment_id>`. Its discovery document is at `<issuer>/.well-known/openid-configuration`.
4004
4004
  * @example https://auth.yourapp.com
4005
4005
  */
4006
4006
  issuer: string;
@@ -4013,6 +4013,7 @@ interface components {
4013
4013
  };
4014
4014
  UpdateOAuthClientDto: {
4015
4015
  name?: string;
4016
+ /** @description Replaces the registered redirect URIs. At least one is required, as at creation: a client with none could never complete a sign-in. */
4016
4017
  redirect_uris?: string[];
4017
4018
  /** @description Where this client may send someone after RP-initiated logout. Matched exactly. An empty array removes every destination, which leaves logout working with nowhere to return to. */
4018
4019
  post_logout_redirect_uris?: string[];
@@ -4053,12 +4054,12 @@ interface components {
4053
4054
  /** @description API key description */
4054
4055
  description?: string;
4055
4056
  /**
4056
- * @description Required. `scoped` enforces the `scopes` array on every authorization check (deny if the requested permission isn't listed). `full_access` bypasses RBAC entirely within the key's Application — every permission is granted. Pick `scoped` whenever possible; `full_access` should be a deliberate choice (use cases: bootstrap automation, trusted backend services that legitimately need App-wide access). `scoped` requires a non-empty `scopes` array; `full_access` forbids `scopes`.
4057
+ * @description Required. `scoped` enforces the `scopes` array on every authorization check (deny if the requested permission isn't listed). `full_access` bypasses RBAC entirely within the key's Application — every permission is granted. Pick `scoped` whenever possible; `full_access` should be a deliberate choice (use cases: bootstrap automation, trusted backend services that legitimately need App-wide access). `scoped` requires a non-empty `scopes` array; `full_access` forbids `scopes`. A scoped API key can create only scoped keys whose scopes it holds itself.
4057
4058
  * @enum {string}
4058
4059
  */
4059
4060
  access_mode: "scoped" | "full_access";
4060
4061
  /** @description Permission scopes this key is authorized for, each one the platform offers (`GET .../api-keys/scopes`). Required and must be non-empty when `access_mode` is `scoped`. Must be omitted when `access_mode` is `full_access`. */
4061
- scopes?: ("hierarchy.view" | "hierarchy.manage" | "rbac.view_roles" | "rbac.manage_roles" | "rbac.view_assignments" | "rbac.manage_assignments" | "permissions.evaluate" | "identity.view" | "identity.manage" | "api_key.view" | "api_key.manage" | "webhook.view" | "webhook.manage" | "oauth_client.view" | "oauth_client.manage")[];
4062
+ scopes?: ("hierarchy.view" | "hierarchy.manage" | "rbac.view_roles" | "rbac.manage_roles" | "rbac.view_assignments" | "rbac.manage_assignments" | "permissions.evaluate" | "identity.view" | "identity.manage" | "api_key.view" | "api_key.manage" | "webhook.view" | "webhook.manage" | "oauth_client.view" | "oauth_client.manage" | "branding.view" | "branding.manage")[];
4062
4063
  /** @description Expiration date (ISO 8601). Omit for no expiration. */
4063
4064
  expires_at?: string;
4064
4065
  };
@@ -7682,7 +7683,7 @@ interface operations {
7682
7683
  "application/json": components["schemas"]["ErrorResponseDto"];
7683
7684
  };
7684
7685
  };
7685
- /** @description The identity is not found, or the session does not belong to it or is already revoked */
7686
+ /** @description The identity is not found, or the session does not belong to it, belongs to another Environment, or is already revoked */
7686
7687
  404: {
7687
7688
  headers: {
7688
7689
  [name: string]: unknown;
@@ -7693,7 +7694,7 @@ interface operations {
7693
7694
  * "error": {
7694
7695
  * "statusCode": 404,
7695
7696
  * "code": null,
7696
- * "message": "The identity is not found, or the session does not belong to it or is already revoked",
7697
+ * "message": "The identity is not found, or the session does not belong to it, belongs to another Environment, or is already revoked",
7697
7698
  * "timestamp": "2026-04-20T12:00:00.000Z",
7698
7699
  * "path": "/api/v1/identities/{id}/sessions/{sessionId}",
7699
7700
  * "method": "DELETE"
@@ -13246,6 +13247,27 @@ interface operations {
13246
13247
  "application/json": components["schemas"]["ErrorResponseDto"];
13247
13248
  };
13248
13249
  };
13250
+ /** @description The caller is an organization administrator and the member also belongs to another organization in the Environment; the second factor is held once per identity, so only the application's administrators may reset it (`organization.member_mfa_shared`) */
13251
+ 409: {
13252
+ headers: {
13253
+ [name: string]: unknown;
13254
+ };
13255
+ content: {
13256
+ /**
13257
+ * @example {
13258
+ * "error": {
13259
+ * "statusCode": 409,
13260
+ * "code": null,
13261
+ * "message": "The caller is an organization administrator and the member also belongs to another organization in the Environment; the second factor is held once per identity, so only the application's administrators may reset it (`organization.member_mfa_shared`)",
13262
+ * "timestamp": "2026-04-20T12:00:00.000Z",
13263
+ * "path": "/api/v1/organizations/{id}/members/{identityId}/mfa/reset",
13264
+ * "method": "POST"
13265
+ * }
13266
+ * }
13267
+ */
13268
+ "application/json": components["schemas"]["ErrorResponseDto"];
13269
+ };
13270
+ };
13249
13271
  };
13250
13272
  };
13251
13273
  ApiOrganizationsController_listInvites: {
@@ -19166,7 +19188,7 @@ interface operations {
19166
19188
  "application/json": components["schemas"]["ErrorResponseDto"];
19167
19189
  };
19168
19190
  };
19169
- /** @description This token is not authorized for this endpoint (wrong principal type — e.g., admin token on identity-only endpoint, or vice versa) */
19191
+ /** @description The bearer token is not an admin or platform token, or the caller lacks the permission this operation checks: a scoped API key without that scope, an admin without the Account capability that covers it, or, for a node-scoped check, a node outside this Environment */
19170
19192
  403: {
19171
19193
  headers: {
19172
19194
  [name: string]: unknown;
@@ -19177,7 +19199,7 @@ interface operations {
19177
19199
  * "error": {
19178
19200
  * "statusCode": 403,
19179
19201
  * "code": null,
19180
- * "message": "This token is not authorized for this endpoint (wrong principal type — e.g., admin token on identity-only endpoint, or vice versa)",
19202
+ * "message": "The bearer token is not an admin or platform token, or the caller lacks the permission this operation checks: a scoped API key without that scope, an admin without the Account capability that covers it, or, for a node-scoped check, a node outside this Environment",
19181
19203
  * "timestamp": "2026-04-20T12:00:00.000Z",
19182
19204
  * "path": "/api/v1/branding",
19183
19205
  * "method": "GET"
@@ -19255,7 +19277,7 @@ interface operations {
19255
19277
  "application/json": components["schemas"]["ErrorResponseDto"];
19256
19278
  };
19257
19279
  };
19258
- /** @description This token is not authorized for this endpoint (wrong principal type — e.g., admin token on identity-only endpoint, or vice versa) */
19280
+ /** @description The bearer token is not an admin or platform token, or the caller lacks the permission this operation checks: a scoped API key without that scope, an admin without the Account capability that covers it, or, for a node-scoped check, a node outside this Environment */
19259
19281
  403: {
19260
19282
  headers: {
19261
19283
  [name: string]: unknown;
@@ -19266,7 +19288,7 @@ interface operations {
19266
19288
  * "error": {
19267
19289
  * "statusCode": 403,
19268
19290
  * "code": null,
19269
- * "message": "This token is not authorized for this endpoint (wrong principal type — e.g., admin token on identity-only endpoint, or vice versa)",
19291
+ * "message": "The bearer token is not an admin or platform token, or the caller lacks the permission this operation checks: a scoped API key without that scope, an admin without the Account capability that covers it, or, for a node-scoped check, a node outside this Environment",
19270
19292
  * "timestamp": "2026-04-20T12:00:00.000Z",
19271
19293
  * "path": "/api/v1/branding",
19272
19294
  * "method": "GET"
@@ -20882,7 +20904,7 @@ interface operations {
20882
20904
  "application/json": components["schemas"]["ErrorResponseDto"];
20883
20905
  };
20884
20906
  };
20885
- /** @description The bearer token is not an admin or platform token, or the caller lacks the permission this operation checks: a scoped API key without that scope, an admin without the Account capability that covers it, or, for a node-scoped check, a node outside this Environment */
20907
+ /** @description The caller lacks `api_key.manage` (a scoped API key without that scope, or an admin without the Account capability that covers it), or is a scoped API key asking for more than it holds: `full_access`, or a scope outside its own (`api_key.access_exceeds_caller`) */
20886
20908
  403: {
20887
20909
  headers: {
20888
20910
  [name: string]: unknown;
@@ -20893,10 +20915,10 @@ interface operations {
20893
20915
  * "error": {
20894
20916
  * "statusCode": 403,
20895
20917
  * "code": null,
20896
- * "message": "The bearer token is not an admin or platform token, or the caller lacks the permission this operation checks: a scoped API key without that scope, an admin without the Account capability that covers it, or, for a node-scoped check, a node outside this Environment",
20918
+ * "message": "The caller lacks `api_key.manage` (a scoped API key without that scope, or an admin without the Account capability that covers it), or is a scoped API key asking for more than it holds: `full_access`, or a scope outside its own (`api_key.access_exceeds_caller`)",
20897
20919
  * "timestamp": "2026-04-20T12:00:00.000Z",
20898
20920
  * "path": "/api/v1/api-keys",
20899
- * "method": "GET"
20921
+ * "method": "POST"
20900
20922
  * }
20901
20923
  * }
20902
20924
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-io/node",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Official TypeScript SDK for Canopy \u2014 hierarchical identity and access management.",
5
5
  "license": "MIT",
6
6
  "author": "Canopy Identity Inc.",