@permute/sdk 0.9.0 → 0.10.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/README.md CHANGED
@@ -20,6 +20,18 @@ bunx @permute/sdk auth logout
20
20
 
21
21
  Choose an organization, optionally limit the connection to one workspace, then select resources and permissions to approve in Permute. The CLI stores renewable credentials in your operating system’s credential store (no plaintext fallback). Active logins have no fixed maximum lifetime and expire after 30 days without renewal; company sign-in rules can require earlier authentication. Logging out revokes access before removing the local credential. Signing in again revokes the previous stored CLI session.
22
22
 
23
+ To request a particular scope, add `--organization ID`, `--workspace ID`, or `--access-file PATH` to login. The JSON file uses the same resource/permission selections as API-key creation:
24
+
25
+ ```json
26
+ {
27
+ "organizationId": "org_...",
28
+ "workspaceId": "wksp_...",
29
+ "permissions": [{ "resourceId": "dbas_...", "resourceType": "database", "level": "read" }]
30
+ }
31
+ ```
32
+
33
+ An SDK login can supply the same object as `new OAuthLogin({ requestedAccess })` before calling `beginDevice()` or `beginBrowser(...)`. The browser prefills the request and permits only narrower selections. When available in the same browser, reauthentication recalls settings from a prior session. Each login still requires confirmation, and current resource permissions apply (saved selections create no resource grants). The browser stores only a non-secret session ID; it does not create another grant or change existing sessions. Logout and Connections disconnect individual logins.
34
+
23
35
  The SDK reuses credentials in memory between requests and reloads the OS store before renewal or when another process changes the saved login. The local auth directory holds only a lock and a non-secret change marker; tokens stay out of ordinary files. Current server permissions and revocation still apply to every request.
24
36
 
25
37
  The CLI prints the issuer and `permute-cli` entry name to stderr before accessing the OS store. macOS controls its own permission dialog, which may identify the Node or Bun process. SDK applications stay silent by default and can supply `onCredentialStoreAccess: (issuer) => { /* show a notice */ }` to `OAuthLogin`; the callback runs once before the default OS store is accessed and receives no credentials.
@@ -130,8 +142,9 @@ Data Flow and operation authoring are REST/SDK only. MCP does not expose their c
130
142
  editing, draft inspection, execution, or publication. Hosted MCP access uses
131
143
  selected sources or a hosted app's current grants and published action bindings,
132
144
  intersected with the user's permissions. App actions require confirmation in
133
- Permute; see the [MCP guide](https://permute.ai/docs/mcp). Flow scheduling and
134
- standalone operation creation are not part of this surface.
145
+ Permute; see the [MCP guide](https://permute.ai/docs/mcp). MCP does not expose flow
146
+ scheduling or standalone operation creation; schedule operations and flows with
147
+ `client.operations.update`.
135
148
 
136
149
  ## Publish write syncs
137
150
 
@@ -881,7 +894,8 @@ account’s access even when the caller has no connector access. Permute enforce
881
894
  declared execution capabilities and validates input/output schemas; the handler
882
895
  defines the operation exposed to callers.
883
896
 
884
- Handlers receive trusted `caller: { id, type }` (`type` is `user` or `apiKey`).
897
+ Handlers receive trusted `caller: { id, type }` (`type` is `user`, `apiKey`, or
898
+ `system`; a scheduled trigger run passes `{ type: 'system', id: '<triggerId>' }`).
885
899
  An action can use that identity and server-side business data to check or deny a
886
900
  request when its rules warrant it (such as an ownership rule or allowed state transition).
887
901
  Apply those checks before the affected reads or writes. They supplement the
@@ -996,6 +1010,49 @@ replay saved responses. Steps with uncertain outcomes block automatic resend and
996
1010
  provider-state reconciliation. There is no automatic rollback, reconciliation API, or
997
1011
  long-running workflow runner. Connector-only actions can use `sources: []`.
998
1012
 
1013
+ ### Schedule triggers
1014
+
1015
+ A trigger invokes a published action automatically. Schedule triggers use the same UTC
1016
+ schedule format as operations and a fixed input which must match the published input contract.
1017
+ Schedule values are whole numbers: `hourly { minute }`, `daily { hour, minute }`,
1018
+ `weekly { hour, minute, dayOfWeek }` (0 is Sunday), and `monthly { hour, minute, dayOfMonth }`.
1019
+ Use `dayOfMonth` 1–28 to run in every month.
1020
+
1021
+ ```ts
1022
+ const trigger = await client.actionTriggers.create('actn_example', {
1023
+ type: 'schedule',
1024
+ config: { schedule: { type: 'daily', hour: 9, minute: 0 }, input: { region: 'EMEA' } },
1025
+ });
1026
+ await client.actionTriggers.update('actn_example', trigger.id, { isEnabled: false });
1027
+ const { items } = await client.actionTriggers.runs('actn_example', trigger.id);
1028
+ ```
1029
+
1030
+ Trigger routes require API key authentication. A trigger has no owner and runs as the workspace
1031
+ system identity, so its runs do not recheck anyone's access. Instead, creating, editing, or enabling a
1032
+ trigger requires invocation permission and authority to delegate every capability which the action
1033
+ declares (SDK permissions, connectors, and sources), and publishing an action with an enabled trigger
1034
+ requires the publisher to be able to invoke the new revision. Every reader of the action can see its
1035
+ triggers, and invokers can change them. Scheduled runs pass `caller: { type: 'system', id: '<triggerId>' }`.
1036
+ Resources which a scheduled run creates through the SDK belong to the action owner (the user who
1037
+ created the action, or the user who created its API key); scheduled runs cannot create or delete
1038
+ hosted resources (managed databases and apps) or create connector sessions. Each run uses the action's current published revision and the
1039
+ request ID `<triggerId>_<slot epoch ms>`, so a retried slot never runs twice. Runs start within about
1040
+ five minutes of their slot. Missed slots run once, as the newest missed slot, and the skipped
1041
+ slots appear as a `businessAction.trigger.skipped` audit event. A deleted or
1042
+ unpublished action, or input which no longer matches the contract, disables the trigger and
1043
+ sets `disabledReason`; enable it again with `update` after fixing the cause. A slot which cannot
1044
+ start for 24 hours, for example because of a persistent platform error, also disables the trigger.
1045
+ Publishing a new action revision does not check trigger input; an incompatible trigger turns
1046
+ off at its next run. A disable records no run, so read `isEnabled` and `disabledReason`; `nextRunAt`
1047
+ is `null` while the trigger is off. Updating `config` replaces the whole config and repeats the
1048
+ access and input checks, like enabling. Pausing with `isEnabled: false` keeps `disabledReason` `null`.
1049
+ `runs` returns the newest 50 runs with status, revision, and failure, but not their output; to keep a
1050
+ run's results, write them inside the handler. Saved receipts belong to their caller, so invoking with a
1051
+ run's request ID runs the action again.
1052
+ Failures inside the action do not disable the trigger and are not retried; the next slot runs as
1053
+ scheduled. They appear in `runs`, as `lastRun` on the trigger, and as a
1054
+ `businessAction.trigger.failed` audit event.
1055
+
999
1056
  ## Hosted app visitor access (next release)
1000
1057
 
1001
1058
  App admins can read `client.apps.access(appId)` and update access with
@@ -1,3 +1,5 @@
1
+ import { type RequestedAccess } from './request.js';
2
+ export type { RequestedAccess } from './request.js';
1
3
  import { PermuteClient } from '../client.js';
2
4
  import { type CredentialStore, type LoginCredentials } from './store.js';
3
5
  export { SystemCredentialStore, type CredentialStore, type LoginCredentials } from './store.js';
@@ -6,6 +8,7 @@ export declare const DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_cod
6
8
  export interface OAuthOptions {
7
9
  issuer?: string;
8
10
  apiUrl?: string;
11
+ requestedAccess?: RequestedAccess;
9
12
  store?: CredentialStore;
10
13
  fetch?: typeof fetch;
11
14
  /** Called once before the default OS credential store is accessed; no tokens are passed. */
@@ -16,6 +19,7 @@ export declare class OAuthLogin {
16
19
  readonly apiUrl: string;
17
20
  private readonly store;
18
21
  private readonly fetchImpl;
22
+ private readonly requested;
19
23
  constructor(options?: OAuthOptions);
20
24
  private endpoint;
21
25
  private post;
@@ -1,3 +1,4 @@
1
+ import { authorizationDetails } from './request.js';
1
2
  import { z } from 'zod';
2
3
  import { createHash, randomBytes } from 'node:crypto';
3
4
  import { setTimeout } from 'node:timers/promises';
@@ -27,11 +28,13 @@ export class OAuthLogin {
27
28
  apiUrl;
28
29
  store;
29
30
  fetchImpl;
31
+ requested;
30
32
  constructor(options = {}) {
31
33
  this.issuer = this.endpoint(options.issuer ?? DEFAULT_ISSUER);
32
34
  this.apiUrl = this.endpoint(options.apiUrl ?? 'https://api.permute.ai');
33
35
  this.store = options.store ?? new SystemCredentialStore(this.issuer, options.onCredentialStoreAccess);
34
36
  this.fetchImpl = options.fetch ?? fetch;
37
+ this.requested = authorizationDetails(options.requestedAccess);
35
38
  }
36
39
  endpoint(value) {
37
40
  const url = new URL(value);
@@ -90,7 +93,7 @@ export class OAuthLogin {
90
93
  return credentials;
91
94
  }
92
95
  async beginDevice() {
93
- const parsed = DeviceSchema.safeParse(await this.post('/cli/device/authorization', { client_id: CLIENT_ID, scope: 'workspace' }));
96
+ const parsed = DeviceSchema.safeParse(await this.post('/cli/device/authorization', { client_id: CLIENT_ID, scope: 'workspace', ...this.requested }));
94
97
  if (!parsed.success)
95
98
  throw new Error('Invalid device login response');
96
99
  const result = parsed.data;
@@ -139,6 +142,7 @@ export class OAuthLogin {
139
142
  url.search = new URLSearchParams({
140
143
  client_id: CLIENT_ID,
141
144
  scope: 'workspace',
145
+ ...this.requested,
142
146
  response_type: 'code',
143
147
  redirect_uri: redirectURI,
144
148
  state,
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ import type { components } from '../generated/openapi.js';
3
+ export type RequestedAccess = components['schemas']['OAuthRequestedAccess'];
4
+ export declare const RequestedAccessSchema: z.ZodCustom<{
5
+ organizationId?: string;
6
+ permissions?: {
7
+ level: 'read' | 'write' | 'admin';
8
+ operations?: ('application.read_metadata' | 'application.update_metadata' | 'application.read_implementation' | 'application.update_draft' | 'application.publish' | 'application.manage_access' | 'application.manage_hosting' | 'application.delete' | 'business_action.read_metadata' | 'business_action.invoke' | 'business_action.read_implementation' | 'business_action.update_draft' | 'business_action.publish' | 'business_action.manage_access' | 'business_action.delegate_access' | 'business_action.delete')[];
9
+ resourceId: string;
10
+ resourceType: string;
11
+ }[];
12
+ workspaceId?: string;
13
+ }, {
14
+ organizationId?: string;
15
+ permissions?: {
16
+ level: 'read' | 'write' | 'admin';
17
+ operations?: ('application.read_metadata' | 'application.update_metadata' | 'application.read_implementation' | 'application.update_draft' | 'application.publish' | 'application.manage_access' | 'application.manage_hosting' | 'application.delete' | 'business_action.read_metadata' | 'business_action.invoke' | 'business_action.read_implementation' | 'business_action.update_draft' | 'business_action.publish' | 'business_action.manage_access' | 'business_action.delegate_access' | 'business_action.delete')[];
18
+ resourceId: string;
19
+ resourceType: string;
20
+ }[];
21
+ workspaceId?: string;
22
+ }>;
23
+ /** RFC 9396 carries structured permissions without inventing a second operation catalog. */
24
+ export declare function authorizationDetails(request: RequestedAccess | undefined): Record<string, string>;
@@ -0,0 +1,15 @@
1
+ import { Ajv } from 'ajv';
2
+ import { z } from 'zod';
3
+ import { authorizationRequestSchema } from '../generated/runtime-contract.js';
4
+ const validate = new Ajv({ strict: false }).compile(authorizationRequestSchema);
5
+ export const RequestedAccessSchema = z.custom((value) => validate(value), 'Invalid requested access');
6
+ /** RFC 9396 carries structured permissions without inventing a second operation catalog. */
7
+ export function authorizationDetails(request) {
8
+ if (!request)
9
+ return {};
10
+ return {
11
+ authorization_details: JSON.stringify([
12
+ { type: 'https://permute.ai/permissions', ...RequestedAccessSchema.parse(request) },
13
+ ]),
14
+ };
15
+ }
package/dist/cli.js CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { readFile } from 'node:fs/promises';
3
+ import { RequestedAccessSchema } from './auth/request.js';
2
4
  import { createServer } from 'node:http';
3
5
  import { spawn } from 'node:child_process';
4
6
  import { parseArgs } from 'node:util';
@@ -75,6 +77,9 @@ export async function runCli(args = process.argv.slice(2)) {
75
77
  allowPositionals: true,
76
78
  options: {
77
79
  issuer: { type: 'string' },
80
+ 'access-file': { type: 'string' },
81
+ organization: { type: 'string' },
82
+ workspace: { type: 'string' },
78
83
  'api-url': { type: 'string' },
79
84
  'device-code': { type: 'boolean' },
80
85
  'no-browser': { type: 'boolean' },
@@ -83,14 +88,21 @@ export async function runCli(args = process.argv.slice(2)) {
83
88
  });
84
89
  const [area, command] = positionals;
85
90
  if (values.help || area !== 'auth' || !['login', 'logout', 'status'].includes(command ?? '')) {
86
- console.log('permute auth login [--device-code] [--no-browser]\npermute auth status\npermute auth logout\n\nUse --issuer and --api-url together for a development environment.');
91
+ console.log('permute auth login [--device-code] [--no-browser] [--organization ID] [--workspace ID] [--access-file PATH]\npermute auth status\npermute auth logout\n\nUse --issuer and --api-url together for a development environment.');
87
92
  if (!values.help)
88
93
  process.exitCode = 1;
89
94
  return;
90
95
  }
91
96
  if (!!values.issuer !== !!values['api-url'])
92
97
  throw new Error('Provide both --issuer and --api-url for another environment.');
98
+ const requestedAccess = {
99
+ ...(values['access-file'] &&
100
+ RequestedAccessSchema.parse(JSON.parse(await readFile(values['access-file'], 'utf8')))),
101
+ ...(values.organization && { organizationId: values.organization }),
102
+ ...(values.workspace && { workspaceId: values.workspace }),
103
+ };
93
104
  const login = new OAuthLogin({
105
+ ...(Object.keys(requestedAccess).length > 0 && { requestedAccess }),
94
106
  issuer: values.issuer,
95
107
  apiUrl: values['api-url'],
96
108
  onCredentialStoreAccess: (issuer) => {
package/dist/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { EmailSender, EmailSendRequest, EmailReceipt, PermissionGrant, PermissionGrantInput, PermissionGrantUpdate, PermissionGrantQuery, PermissionGrantList, PermissionGrantRevoked, PermissionInspectionInput, PermissionInspection, AccessRequest, AccessRequestInput, AccessRequestQuery, AccessRequestList, AccessRequestDecision } from './types.js';
1
+ import type { EmailSender, EmailSendRequest, EmailReceipt, PermissionGrant, PermissionGrantInput, PermissionGrantUpdate, PermissionGrantQuery, PermissionGrantList, PermissionGrantRevoked, PermissionInspectionInput, PermissionInspection, AccessRequest, AccessRequestInput, AccessRequestQuery, AccessRequestList, AccessRequestDecision, MemberList, OrganizationMemberInvite, WorkspaceMemberInvite, OrganizationMemberRoleUpdate, WorkspaceMemberRoleUpdate, MemberInvitation, MemberInvitationDelivery, MemberRoleUpdated, MemberRemoved, Organization, OrganizationList } from './types.js';
2
2
  export { PermuteApiError } from './errors.js';
3
- import type { BusinessAction, BusinessActionDetail, BusinessActionList, ActionCreateRequest, ActionDefinition, ActionDefinitionSaveRequest, ActionDefinitionSaved, ActionRequest, ActionReceipt, ActionPublicationRequest, ActionPublicationRequestInput, ActionPublicationRequestList, ActionPublicationDecision } from './types.js';
3
+ import type { BusinessAction, BusinessActionDetail, BusinessActionList, ActionCreateRequest, ActionDefinition, ActionDefinitionSaveRequest, ActionDefinitionSaved, ActionRequest, ActionReceipt, ActionTrigger, ActionTriggerList, ActionTriggerCreateRequest, ActionTriggerUpdateRequest, ActionTriggerRunList, ActionPublicationRequest, ActionPublicationRequestInput, ActionPublicationRequestList, ActionPublicationDecision } from './types.js';
4
4
  import type { ConnectorSession, ConnectorSessionCreated, ConnectorSessionCreateRequest, Database, AppPublicationRequest, AppPublicationRequestInput, AppPublicationRequestList, AppPublicationDecision, AppDraft, AppAccess, AppAddressSettings, AppAddressSetRequest, AppAccessSetRequest, AppDraftList, AppCreateRequest, AppUpdateRequest, AppDeleted, AppUploadsRequest, AppUploads, AppSiteSaveRequest, AppSourceFileQuery, AppSourceFileContent, AppGrants, AppGrantSetRequest, AppUploadFile, DatabaseList, DatabaseCreateRequest, DatabaseDefinition, DatabaseDefinitionSaveRequest, DatabaseDefinitionSaved, DatabaseTables, DatabaseTableCreateRequest, DatabaseTableCreated, DatabaseRecords, DatabaseRecordsQuery, DatabaseTransactionRequest, DatabaseTransactionReceipt, WriteSync, WriteSyncCreateRequest, WriteSyncUpdateRequest, WriteSyncDetail, WriteSyncList, WriteSyncDeleted, WriteSyncDestinations, WriteSyncRun, WriteSyncRunList, WriteSyncRunQuery, WriteSyncExecution, ResourceVersionState, ResourceRevisionList, ResourceRevision, ResourceVersionRequest, ResourcePublishRequest, Fact, FactList, FactWriteRequest, SkillList, SkillContent, SkillWriteRequest, KnowledgeWriteResponse, OperationRunGroup, OperationRunQuery, Operation, OperationList, OperationListQuery, OperationSource, OperationSourceUpdateRequest, OperationUpdateRequest, OperationRunRequest, OperationRunStarted, OperationRunList, OperationRunOutput, DataFlow, DataFlowCreateRequest, DataFlowCommandRequest, DataFlowCommandResponse, Workspace, WorkspaceCreateRequest, WorkspaceDeleteResponse, WorkspaceList, WorkspaceUpdateRequest, AnswerRequest, AnswerResponse, PermuteClientOptions, PermuteRequestOptions, QueryRequest, SearchRequest, SourceDetail, CatalogTable, SourceList, CustomDatasetCreateRequest, DatasetDeleteResponse, DatasetResponse, FileDatasetCreateRequest, FileDatasetMetadataCreateRequest, FileDatasetResponse, DatasetRows, DatasetCsvUploadOptions, CustomDatasetResponse, DashboardSource, DashboardSourceUpdateRequest } from './types.js';
5
5
  type DatasetWriteInput = {
6
6
  tables: Record<string, DatasetRows>;
@@ -62,6 +62,24 @@ export interface SkillsClient {
62
62
  delete(path: string, options?: PermuteRequestOptions): Promise<KnowledgeWriteResponse>;
63
63
  }
64
64
  export declare class PermuteClient {
65
+ readonly organizations: {
66
+ list(options?: PermuteRequestOptions): Promise<OrganizationList>;
67
+ get(organizationId: string, options?: PermuteRequestOptions): Promise<Organization>;
68
+ };
69
+ readonly organizationMembers: {
70
+ list(organizationId: string, options?: PermuteRequestOptions): Promise<MemberList>;
71
+ invite(organizationId: string, body: OrganizationMemberInvite, options?: PermuteRequestOptions): Promise<MemberInvitation>;
72
+ resendInvitation(organizationId: string, userId: string, options?: PermuteRequestOptions): Promise<MemberInvitationDelivery>;
73
+ updateRole(organizationId: string, userId: string, body: OrganizationMemberRoleUpdate, options?: PermuteRequestOptions): Promise<MemberRoleUpdated>;
74
+ remove(organizationId: string, userId: string, options?: PermuteRequestOptions): Promise<MemberRemoved>;
75
+ };
76
+ readonly workspaceMembers: {
77
+ list(workspaceId: string, options?: PermuteRequestOptions): Promise<MemberList>;
78
+ invite(workspaceId: string, body: WorkspaceMemberInvite, options?: PermuteRequestOptions): Promise<MemberInvitation>;
79
+ resendInvitation(workspaceId: string, userId: string, options?: PermuteRequestOptions): Promise<MemberInvitationDelivery>;
80
+ updateRole(workspaceId: string, userId: string, body: WorkspaceMemberRoleUpdate, options?: PermuteRequestOptions): Promise<MemberRoleUpdated>;
81
+ remove(workspaceId: string, userId: string, options?: PermuteRequestOptions): Promise<MemberRemoved>;
82
+ };
65
83
  readonly permissions: {
66
84
  update(permissionId: string, body: PermissionGrantUpdate, options?: PermuteRequestOptions): Promise<PermissionGrant>;
67
85
  list(query?: PermissionGrantQuery, options?: PermuteRequestOptions): Promise<PermissionGrantList>;
@@ -102,6 +120,14 @@ export declare class PermuteClient {
102
120
  saveDefinition(actionId: string, body: ActionDefinitionSaveRequest, options?: PermuteRequestOptions): Promise<ActionDefinitionSaved>;
103
121
  invoke(actionId: string, body: ActionRequest, options?: PermuteRequestOptions): Promise<ActionReceipt>;
104
122
  };
123
+ readonly actionTriggers: {
124
+ list(actionId: string, options?: PermuteRequestOptions): Promise<ActionTriggerList>;
125
+ create(actionId: string, body: ActionTriggerCreateRequest, options?: PermuteRequestOptions): Promise<ActionTrigger>;
126
+ get(actionId: string, triggerId: string, options?: PermuteRequestOptions): Promise<ActionTrigger>;
127
+ update(actionId: string, triggerId: string, body: ActionTriggerUpdateRequest, options?: PermuteRequestOptions): Promise<ActionTrigger>;
128
+ delete(actionId: string, triggerId: string, options?: PermuteRequestOptions): Promise<ActionTrigger>;
129
+ runs(actionId: string, triggerId: string, options?: PermuteRequestOptions): Promise<ActionTriggerRunList>;
130
+ };
105
131
  readonly emails: {
106
132
  get(options?: PermuteRequestOptions): Promise<EmailSender>;
107
133
  /** Only callable from Business Actions through platform.withStep(stepId). */
package/dist/client.js CHANGED
@@ -295,6 +295,9 @@ export class PermuteFileDataset {
295
295
  }
296
296
  }
297
297
  export class PermuteClient {
298
+ organizations;
299
+ organizationMembers;
300
+ workspaceMembers;
298
301
  permissions;
299
302
  accessRequests;
300
303
  revisions;
@@ -302,6 +305,7 @@ export class PermuteClient {
302
305
  skills;
303
306
  organizationSkills;
304
307
  actions;
308
+ actionTriggers;
305
309
  emails;
306
310
  databases;
307
311
  apps;
@@ -349,6 +353,24 @@ export class PermuteClient {
349
353
  delete: this.bindMethod('permissionsDelete', (request) => (permissionId, options) => request({ pathParams: { permissionId } }, options)),
350
354
  inspect: this.bindMethod('permissionsInspect', (request) => (body, options) => request({ body }, options)),
351
355
  };
356
+ this.organizations = {
357
+ list: this.bindMethod('organizationsList', (apiRequest) => (options) => apiRequest({}, options)),
358
+ get: this.bindMethod('organizationsGet', (apiRequest) => (organizationId, options) => apiRequest({ pathParams: { organizationId } }, options)),
359
+ };
360
+ this.organizationMembers = {
361
+ list: this.bindMethod('organizationMembersList', (apiRequest) => (organizationId, options) => apiRequest({ pathParams: { organizationId } }, options)),
362
+ invite: this.bindMethod('organizationMembersInvite', (request) => (organizationId, body, options) => request({ pathParams: { organizationId }, body }, options)),
363
+ resendInvitation: this.bindMethod('organizationMembersResendInvitation', (request) => (organizationId, userId, options) => request({ pathParams: { organizationId, userId } }, options)),
364
+ updateRole: this.bindMethod('organizationMembersUpdateRole', (request) => (organizationId, userId, body, options) => request({ pathParams: { organizationId, userId }, body }, options)),
365
+ remove: this.bindMethod('organizationMembersRemove', (request) => (organizationId, userId, options) => request({ pathParams: { organizationId, userId } }, options)),
366
+ };
367
+ this.workspaceMembers = {
368
+ list: this.bindMethod('workspaceMembersList', (apiRequest) => (workspaceId, options) => apiRequest({ pathParams: { workspaceId } }, options)),
369
+ invite: this.bindMethod('workspaceMembersInvite', (request) => (workspaceId, body, options) => request({ pathParams: { workspaceId }, body }, options)),
370
+ resendInvitation: this.bindMethod('workspaceMembersResendInvitation', (request) => (workspaceId, userId, options) => request({ pathParams: { workspaceId, userId } }, options)),
371
+ updateRole: this.bindMethod('workspaceMembersUpdateRole', (request) => (workspaceId, userId, body, options) => request({ pathParams: { workspaceId, userId }, body }, options)),
372
+ remove: this.bindMethod('workspaceMembersRemove', (request) => (workspaceId, userId, options) => request({ pathParams: { workspaceId, userId } }, options)),
373
+ };
352
374
  this.accessRequests = {
353
375
  create: this.bindMethod('accessRequestsCreate', (request) => (body, options) => request({ body }, options)),
354
376
  list: this.bindMethod('accessRequestsList', (request) => (query, options) => request({ query }, options)),
@@ -388,6 +410,14 @@ export class PermuteClient {
388
410
  saveDefinition: this.bindMethod('actionDefinitionSave', (apiRequest) => (actionId, body, options) => apiRequest({ pathParams: { actionId }, body }, options)),
389
411
  invoke: this.bindMethod('actionsInvoke', (apiRequest) => (actionId, body, options) => apiRequest({ pathParams: { actionId }, body }, options)),
390
412
  };
413
+ this.actionTriggers = {
414
+ list: this.bindMethod('actionTriggersList', (apiRequest) => (actionId, options) => apiRequest({ pathParams: { actionId } }, options)),
415
+ create: this.bindMethod('actionTriggersCreate', (apiRequest) => (actionId, body, options) => apiRequest({ pathParams: { actionId }, body }, options)),
416
+ get: this.bindMethod('actionTriggersGet', (apiRequest) => (actionId, triggerId, options) => apiRequest({ pathParams: { actionId, triggerId } }, options)),
417
+ update: this.bindMethod('actionTriggersUpdate', (apiRequest) => (actionId, triggerId, body, options) => apiRequest({ pathParams: { actionId, triggerId }, body }, options)),
418
+ delete: this.bindMethod('actionTriggersDelete', (apiRequest) => (actionId, triggerId, options) => apiRequest({ pathParams: { actionId, triggerId } }, options)),
419
+ runs: this.bindMethod('actionTriggerRunsList', (apiRequest) => (actionId, triggerId, options) => apiRequest({ pathParams: { actionId, triggerId } }, options)),
420
+ };
391
421
  this.emails = {
392
422
  get: this.bindMethod('emailsGet', (request) => (options) => request({}, options)),
393
423
  send: this.bindMethod('emailsSend', (request) => (body, options) => request({ body }, options)),
@@ -715,7 +745,7 @@ export class PermuteClient {
715
745
  ...options?.headers,
716
746
  };
717
747
  const workspaceId = options?.workspaceId ?? this.workspaceId;
718
- if (route.scope === 'workspace' && workspaceId) {
748
+ if (route.scope === 'workspace' && workspaceId && !input.pathParams?.workspaceId) {
719
749
  headers['X-Workspace-Id'] = workspaceId;
720
750
  }
721
751
  if (body !== undefined) {