@vectros-ai/mcp-server 0.12.0 → 0.14.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/dist/index.js CHANGED
@@ -103,6 +103,7 @@ __export2(api_exports, {
103
103
  TooManyRequestsError: () => TooManyRequestsError,
104
104
  TruncationWarningEvent: () => TruncationWarningEvent,
105
105
  UnauthorizedError: () => UnauthorizedError,
106
+ UserExistsResponse: () => UserExistsResponse,
106
107
  UserRequest: () => UserRequest,
107
108
  UserResponse: () => UserResponse,
108
109
  auth: () => auth_exports,
@@ -779,6 +780,14 @@ var TruncationWarningEvent;
779
780
  ContextWindowBudget: "context_window_budget"
780
781
  };
781
782
  })(TruncationWarningEvent || (TruncationWarningEvent = {}));
783
+ var UserExistsResponse;
784
+ ((UserExistsResponse2) => {
785
+ UserExistsResponse2.Status = {
786
+ Active: "ACTIVE",
787
+ Suspended: "SUSPENDED",
788
+ Pending: "PENDING"
789
+ };
790
+ })(UserExistsResponse || (UserExistsResponse = {}));
782
791
  var UserRequest;
783
792
  ((UserRequest2) => {
784
793
  UserRequest2.Status = {
@@ -2471,7 +2480,7 @@ var AuthClient = class {
2471
2480
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/access-log");
2472
2481
  }
2473
2482
  /**
2474
- * Lists all of your scoped API keys (`ssk_*`) across both your live and test environments. Revoked keys are excluded. Requires the `keys:r` scope.
2483
+ * Lists your scoped API keys (`ssk_*`) in your credential's own environment — a live key lists live keys, a test key lists test keys. Revoked keys are excluded. Requires the `keys:r` scope.
2475
2484
  *
2476
2485
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
2477
2486
  *
@@ -2522,7 +2531,7 @@ var AuthClient = class {
2522
2531
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/keys/scoped");
2523
2532
  }
2524
2533
  /**
2525
- * Creates a scoped API key (an `ssk_*` secret) that inherits its permissions from an existing access profile in your account. The call is idempotent on the combination of tenant, context, user, and key name: re-issuing the same request returns the existing key WITHOUT re-disclosing its raw secret. The raw key is returned ONLY in this response — store it securely, as it cannot be retrieved again. Requires the `keys:c` scope.
2534
+ * Creates a scoped API key (an `ssk_*` secret) that inherits its permissions from an existing access profile in your account. The call is idempotent on the combination of tenant, context, user, and key name: re-issuing the same request returns the existing key WITHOUT re-disclosing its raw secret. The raw key is returned ONLY in this response — store it securely, as it cannot be retrieved again. Requires the `keys:c` scope. If you use a scoped credential, `keys:c` alone is not sufficient: because the minted key is durably bound to the profile you name, the profile's effective scopes may not exceed your own, and you may only mint against a profile whose `identityOverrides` values your own identity holds. A root API key (`sk_`) is exempt from both bounds.
2526
2535
  *
2527
2536
  * @param {Vectros.CreateScopedKeyRequest} request
2528
2537
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -2854,7 +2863,7 @@ var AuthClient = class {
2854
2863
  );
2855
2864
  }
2856
2865
  /**
2857
- * Creates a new access profile under the given app context. This call is idempotent by `principalId`: if a profile with the same `principalId` already exists, the existing profile is returned (with status 200) instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing profile was returned) tells the two apart. To overwrite an existing profile's `scopes`/`roleId`, `identityOverrides`, and `status` instead of returning it unchanged, set `?upsert=true` (this also requires the `profiles:u` scope). You must provide exactly one of `scopes` (an inline list of scopes) or `roleId` (a reference to a role); supplying both, or neither, is rejected. `identityOverrides` is keyed by ownership namespace in `scope:<namespace>` form — `scope:org` and `scope:client` for the reserved namespaces, or any namespace you have registered — and may name at most two; any other key (including the account identifier or `userId`) is rejected. If you use a scoped credential, the profile's effective scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
2866
+ * Creates a new access profile under the given app context. This call is idempotent by `principalId`: if a profile with the same `principalId` already exists, the existing profile is returned (with status 200) instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing profile was returned) tells the two apart. To overwrite an existing profile's `scopes`/`roleId`, `identityOverrides`, and `status` instead of returning it unchanged, set `?upsert=true` (this also requires the `profiles:u` scope, and applies the same `identityOverrides` bounds the update endpoint documents — a scoped credential may not repoint or clear an identity value it does not itself hold). You must provide exactly one of `scopes` (an inline list of scopes) or `roleId` (a reference to a role); supplying both, or neither, is rejected. `identityOverrides` is keyed by ownership namespace in `scope:<namespace>` form — `scope:org` and `scope:client` for the reserved namespaces, or any namespace you have registered — and may name at most two; any other key (including the account identifier or `userId`) is rejected. If you use a scoped credential, the profile's effective scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
2858
2867
  *
2859
2868
  * @param {Vectros.CreateAccessProfileRequest} request
2860
2869
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -3272,11 +3281,12 @@ var AuthClient = class {
3272
3281
  );
3273
3282
  }
3274
3283
  /**
3275
- * Updates an access profile. This is a partial update: any field you omit (or send as null) keeps its existing value. A profile must reference either inline `scopes` or a `roleId`, never both — so setting `scopes` clears any `roleId`, and setting `roleId` clears any inline `scopes`. The `contextId` and `principalId` are immutable. Status changes (for example active to suspended) take effect within about five minutes. If you use a scoped credential, the profile's effective scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:u` scope.
3284
+ * Updates an access profile. This is a partial update: any field you omit (or send as null) keeps its existing value. A profile must reference either inline `scopes` or a `roleId`, never both — so setting `scopes` clears any `roleId`, and setting `roleId` clears any inline `scopes`. The `contextId` and `principalId` are immutable. Status changes (for example active to suspended) take effect within about five minutes. If you use a scoped credential, the profile's effective scopes may not exceed your own, and its `identityOverrides` are bounded twice: you may only set a value your own identity holds, and you may only change or clear a value the profile already holds if that value is yours as well. Repointing or clearing another principal's established identity therefore returns 403. A root API key (`sk_`) is exempt. If you set `roleId`, it must reference a role that already exists in this context. Requires the `profiles:u` scope.
3276
3285
  *
3277
3286
  * @param {Vectros.UpdateAccessProfileRequest} request
3278
3287
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
3279
3288
  *
3289
+ * @throws {@link Vectros.BadRequestError}
3280
3290
  * @throws {@link Vectros.ForbiddenError}
3281
3291
  * @throws {@link Vectros.NotFoundError}
3282
3292
  * @throws {@link Vectros.TooManyRequestsError}
@@ -3323,6 +3333,8 @@ var AuthClient = class {
3323
3333
  }
3324
3334
  if (_response.error.reason === "status-code") {
3325
3335
  switch (_response.error.statusCode) {
3336
+ case 400:
3337
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
3326
3338
  case 403:
3327
3339
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
3328
3340
  case 404:
@@ -3345,7 +3357,7 @@ var AuthClient = class {
3345
3357
  );
3346
3358
  }
3347
3359
  /**
3348
- * Deletes an access profile. Within about five minutes (the access-profile cache lifetime), token minting for this principal in this context will be denied. Requires the `profiles:d` scope.
3360
+ * Deletes an access profile. Within about five minutes (the access-profile cache lifetime), token minting for this principal in this context will be denied. If you use a scoped credential and the profile carries `identityOverrides`, you may only delete it when you hold those values yourself — deleting a profile removes its identity, so the same bound applies as when clearing it. A profile with no `identityOverrides` is unaffected, and a root API key (`sk_`) is exempt. Requires the `profiles:d` scope.
3349
3361
  *
3350
3362
  * @param {Vectros.DeleteAccessProfileRequest} request
3351
3363
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4014,6 +4026,254 @@ var AuthClient = class {
4014
4026
  }
4015
4027
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/usage");
4016
4028
  }
4029
+ /**
4030
+ * Retrieves a single registered issuer by issuerId. Requires a root API key or the bootstrap's provisioning capability.
4031
+ *
4032
+ * @param {Vectros.GetIssuerRequest} request
4033
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4034
+ *
4035
+ * @throws {@link Vectros.ForbiddenError}
4036
+ * @throws {@link Vectros.NotFoundError}
4037
+ *
4038
+ * @example
4039
+ * await client.auth.getIssuer({
4040
+ * issuerId: "auth0-prod"
4041
+ * })
4042
+ */
4043
+ getIssuer(request, requestOptions) {
4044
+ return HttpResponsePromise.fromPromise(this.__getIssuer(request, requestOptions));
4045
+ }
4046
+ async __getIssuer(request, requestOptions) {
4047
+ const { issuerId } = request;
4048
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4049
+ const _headers = mergeHeaders(
4050
+ _authRequest.headers,
4051
+ this._options?.headers,
4052
+ requestOptions?.headers
4053
+ );
4054
+ const _response = await fetcher({
4055
+ url: url_exports.join(
4056
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4057
+ `v1/auth/issuers/${url_exports.encodePathParam(issuerId)}`
4058
+ ),
4059
+ method: "GET",
4060
+ headers: _headers,
4061
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4062
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4063
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4064
+ abortSignal: requestOptions?.abortSignal,
4065
+ fetchFn: this._options?.fetch,
4066
+ logging: this._options.logging
4067
+ });
4068
+ if (_response.ok) {
4069
+ return { data: _response.body, rawResponse: _response.rawResponse };
4070
+ }
4071
+ if (_response.error.reason === "status-code") {
4072
+ switch (_response.error.statusCode) {
4073
+ case 403:
4074
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4075
+ case 404:
4076
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4077
+ default:
4078
+ throw new VectrosError({
4079
+ statusCode: _response.error.statusCode,
4080
+ body: _response.error.body,
4081
+ rawResponse: _response.rawResponse
4082
+ });
4083
+ }
4084
+ }
4085
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/issuers/{issuerId}");
4086
+ }
4087
+ /**
4088
+ * Deregisters a trusted third-party IdP issuer. Requires a root API key or the bootstrap's provisioning capability. Unconditional — unlike some other registry deletes on this API, this does not check for or block on any downstream reference. Any user account already created via this issuer (by a prior self-signup or accepted invite) is NOT deleted or modified, but that user can no longer obtain a NEW token this way once you deregister the issuer — every `POST /v1/auth/token/exchange` call against it will 404, with no distinction between an unknown issuer and one you just deregistered. Register a replacement issuer, or restore this one, before your users' current tokens expire if you want their access to continue uninterrupted.
4089
+ *
4090
+ * @param {Vectros.DeleteIssuerRequest} request
4091
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4092
+ *
4093
+ * @throws {@link Vectros.ForbiddenError}
4094
+ * @throws {@link Vectros.NotFoundError}
4095
+ * @throws {@link Vectros.TooManyRequestsError}
4096
+ *
4097
+ * @example
4098
+ * await client.auth.deleteIssuer({
4099
+ * issuerId: "auth0-prod"
4100
+ * })
4101
+ */
4102
+ deleteIssuer(request, requestOptions) {
4103
+ return HttpResponsePromise.fromPromise(this.__deleteIssuer(request, requestOptions));
4104
+ }
4105
+ async __deleteIssuer(request, requestOptions) {
4106
+ const { issuerId } = request;
4107
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4108
+ const _headers = mergeHeaders(
4109
+ _authRequest.headers,
4110
+ this._options?.headers,
4111
+ requestOptions?.headers
4112
+ );
4113
+ const _response = await fetcher({
4114
+ url: url_exports.join(
4115
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4116
+ `v1/auth/issuers/${url_exports.encodePathParam(issuerId)}`
4117
+ ),
4118
+ method: "DELETE",
4119
+ headers: _headers,
4120
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4121
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4122
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4123
+ abortSignal: requestOptions?.abortSignal,
4124
+ fetchFn: this._options?.fetch,
4125
+ logging: this._options.logging
4126
+ });
4127
+ if (_response.ok) {
4128
+ return { data: void 0, rawResponse: _response.rawResponse };
4129
+ }
4130
+ if (_response.error.reason === "status-code") {
4131
+ switch (_response.error.statusCode) {
4132
+ case 403:
4133
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4134
+ case 404:
4135
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4136
+ case 429:
4137
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4138
+ default:
4139
+ throw new VectrosError({
4140
+ statusCode: _response.error.statusCode,
4141
+ body: _response.error.body,
4142
+ rawResponse: _response.rawResponse
4143
+ });
4144
+ }
4145
+ }
4146
+ return handleNonStatusCodeError(
4147
+ _response.error,
4148
+ _response.rawResponse,
4149
+ "DELETE",
4150
+ "/v1/auth/issuers/{issuerId}"
4151
+ );
4152
+ }
4153
+ /**
4154
+ * Returns the issuers registered in your tenant. Requires a root API key or the bootstrap's provisioning capability. Returns a `{data, nextCursor}` envelope.
4155
+ *
4156
+ * @param {Vectros.ListIssuersRequest} request
4157
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4158
+ *
4159
+ * @throws {@link Vectros.ForbiddenError}
4160
+ *
4161
+ * @example
4162
+ * await client.auth.listIssuers()
4163
+ */
4164
+ listIssuers(request = {}, requestOptions) {
4165
+ return HttpResponsePromise.fromPromise(this.__listIssuers(request, requestOptions));
4166
+ }
4167
+ async __listIssuers(request = {}, requestOptions) {
4168
+ const { startFrom, limit } = request;
4169
+ const _queryParams = {
4170
+ startFrom,
4171
+ limit
4172
+ };
4173
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4174
+ const _headers = mergeHeaders(
4175
+ _authRequest.headers,
4176
+ this._options?.headers,
4177
+ requestOptions?.headers
4178
+ );
4179
+ const _response = await fetcher({
4180
+ url: url_exports.join(
4181
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4182
+ "v1/auth/issuers"
4183
+ ),
4184
+ method: "GET",
4185
+ headers: _headers,
4186
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4187
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4188
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4189
+ abortSignal: requestOptions?.abortSignal,
4190
+ fetchFn: this._options?.fetch,
4191
+ logging: this._options.logging
4192
+ });
4193
+ if (_response.ok) {
4194
+ return { data: _response.body, rawResponse: _response.rawResponse };
4195
+ }
4196
+ if (_response.error.reason === "status-code") {
4197
+ switch (_response.error.statusCode) {
4198
+ case 403:
4199
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4200
+ default:
4201
+ throw new VectrosError({
4202
+ statusCode: _response.error.statusCode,
4203
+ body: _response.error.body,
4204
+ rawResponse: _response.rawResponse
4205
+ });
4206
+ }
4207
+ }
4208
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/issuers");
4209
+ }
4210
+ /**
4211
+ * Registers a trusted third-party IdP issuer that BYO-IdP token exchange (`POST /v1/auth/token/exchange`) may accept a `subject_token` from. Requires a root API key or the CLI bootstrap's provisioning capability — never an ordinary partner-grantable scope. Idempotent by `issuerId` within your tenant; the `(issuer, audience)` pair must not already be registered by a different issuerId/tenant.
4212
+ *
4213
+ * @param {Vectros.IssuerRequest} request
4214
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4215
+ *
4216
+ * @throws {@link Vectros.BadRequestError}
4217
+ * @throws {@link Vectros.ForbiddenError}
4218
+ * @throws {@link Vectros.TooManyRequestsError}
4219
+ *
4220
+ * @example
4221
+ * await client.auth.registerIssuer({
4222
+ * issuerId: "auth0-prod",
4223
+ * issuer: "https://your-tenant.us.auth0.com/",
4224
+ * jwksUri: "https://your-tenant.us.auth0.com/.well-known/jwks.json",
4225
+ * audience: "https://api.your-app.example.com",
4226
+ * contextId: "casework"
4227
+ * })
4228
+ */
4229
+ registerIssuer(request, requestOptions) {
4230
+ return HttpResponsePromise.fromPromise(this.__registerIssuer(request, requestOptions));
4231
+ }
4232
+ async __registerIssuer(request, requestOptions) {
4233
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4234
+ const _headers = mergeHeaders(
4235
+ _authRequest.headers,
4236
+ this._options?.headers,
4237
+ requestOptions?.headers
4238
+ );
4239
+ const _response = await fetcher({
4240
+ url: url_exports.join(
4241
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4242
+ "v1/auth/issuers"
4243
+ ),
4244
+ method: "POST",
4245
+ headers: _headers,
4246
+ contentType: "application/json",
4247
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4248
+ requestType: "json",
4249
+ body: request,
4250
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4251
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4252
+ abortSignal: requestOptions?.abortSignal,
4253
+ fetchFn: this._options?.fetch,
4254
+ logging: this._options.logging
4255
+ });
4256
+ if (_response.ok) {
4257
+ return { data: _response.body, rawResponse: _response.rawResponse };
4258
+ }
4259
+ if (_response.error.reason === "status-code") {
4260
+ switch (_response.error.statusCode) {
4261
+ case 400:
4262
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
4263
+ case 403:
4264
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4265
+ case 429:
4266
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4267
+ default:
4268
+ throw new VectrosError({
4269
+ statusCode: _response.error.statusCode,
4270
+ body: _response.error.body,
4271
+ rawResponse: _response.rawResponse
4272
+ });
4273
+ }
4274
+ }
4275
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/auth/issuers");
4276
+ }
4017
4277
  /**
4018
4278
  * Returns the identity bound to your credential — your account, principal type, key id, and scope details — so you can confirm who you are authenticated as and that the credential is valid. MCP clients use this to render "signed in as ..." in a chat UI without a separate identity endpoint.
4019
4279
  *
@@ -4199,7 +4459,7 @@ var AuthClient = class {
4199
4459
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/auth/token");
4200
4460
  }
4201
4461
  /**
4202
- * Invite a new member to one of your app contexts by email. Creates a pending user with a pre-resolved access profile (their permissions on accept) and signs an invitation token. This call is idempotent on the combination of context and email: re-inviting the same email in the same context rotates the token and resends the invitation rather than creating a duplicate. Returns HTTP 201 on a new invite or a successful resend. Returns 409 if that email already belongs to an active or suspended member of the app context, or already has an identity elsewhere in your account (an email can currently belong to only one tenant per account, i.e. your test and live environments cannot share an email). When `sendEmail` is false, the response includes the raw token and a ready-to-use accept link so you can deliver the invitation through your own email provider. Requires the `admin:users` scope.
4462
+ * Invite a new member to one of your app contexts by email. Creates a pending user with a pre-resolved access profile (their permissions on accept) and signs an invitation token. This call is idempotent on the combination of context and email: re-inviting the same email in the same context rotates the token and resends the invitation rather than creating a duplicate — this requires the `users:r` and `users:u` scopes in addition to `users:c`, because resending rotates a credential on an existing invitation and invalidates any link already sent. Without them the collision returns 409 instead, with no invitation details and no change to the outstanding invitation. Returns HTTP 201 on a new invite or a successful resend. Returns 409 if that email already belongs to an active or suspended member of the app context, or already has an identity elsewhere in your account (an email can currently belong to only one tenant per account, i.e. your test and live environments cannot share an email). When `sendEmail` is false, the response includes the raw token and a ready-to-use accept link so you can deliver the invitation through your own email provider. Requires the `users:c` scope.
4203
4463
  *
4204
4464
  * @param {Vectros.CreateInviteRequest} request
4205
4465
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4270,7 +4530,7 @@ var AuthClient = class {
4270
4530
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/users/invite");
4271
4531
  }
4272
4532
  /**
4273
- * Resend an outstanding invitation, identified by its email and app context. Rotates the invitation token and extends its expiry, then (when `sendEmail` is true) re-delivers the email. Rotating the token invalidates any previously issued link for this invitation, so only the newest link works. The invitee's pending permissions are left unchanged. Requires the `admin:users` scope.
4533
+ * Resend an outstanding invitation, identified by its email and app context. Rotates the invitation token and extends its expiry, then (when `sendEmail` is true) re-delivers the email. Rotating the token invalidates any previously issued link for this invitation, so only the newest link works. The invitee's pending permissions are left unchanged. Because this rotates a credential on an existing invitation, it requires the `users:c`, `users:r` and `users:u` scopes.
4274
4534
  *
4275
4535
  * @param {Vectros.CreateInviteRequest} request
4276
4536
  * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -4337,6 +4597,72 @@ var AuthClient = class {
4337
4597
  }
4338
4598
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/users/invite/resend");
4339
4599
  }
4600
+ /**
4601
+ * RFC 8693 OAuth 2.0 Token Exchange. Trades a JWT issued by a third-party identity provider you've registered (`POST /v1/auth/issuers`) for a Vectros `st_*` scoped bearer token — no Vectros credential required to call this endpoint. The exchanged token's scope is resolved entirely server-side from the matched user's access profile; this endpoint accepts no caller-supplied scope, resource, or audience parameter (RFC 8693 §2.1's `resource`/`audience`/`scope` are not used in v1 — the registered `(issuer, audience)` pair alone pins the target tenant and app context). On a first-time login (no existing Vectros identity for this subject), two opt-in binding paths exist: `invite_token` (a `PENDING` sub-user invitation), and — if the registration declares one or more self-signup policies — `signup_type` (a brand-new user is created and bound to the policy's configured role). If `invite_token` is present at all, it is the ONLY path tried — a failed invite never falls through to self-signup. Neither field is required for a subject with an existing identity. Uses the OAuth-standard error envelope (`{"error":..., "error_description":...}`, RFC 6749 §5.2), NOT this API's usual `{"message":...}` shape — its client is generic OAuth tooling, not the Vectros SDK.
4602
+ *
4603
+ * @param {Vectros.TokenExchangeRequest} request
4604
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4605
+ *
4606
+ * @throws {@link Vectros.BadRequestError}
4607
+ * @throws {@link Vectros.UnauthorizedError}
4608
+ * @throws {@link Vectros.ForbiddenError}
4609
+ * @throws {@link Vectros.NotFoundError}
4610
+ * @throws {@link Vectros.TooManyRequestsError}
4611
+ *
4612
+ * @example
4613
+ * await client.auth.exchangeToken({
4614
+ * grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
4615
+ * subject_token: "subject_token",
4616
+ * subject_token_type: "urn:ietf:params:oauth:token-type:jwt"
4617
+ * })
4618
+ */
4619
+ exchangeToken(request, requestOptions) {
4620
+ return HttpResponsePromise.fromPromise(this.__exchangeToken(request, requestOptions));
4621
+ }
4622
+ async __exchangeToken(request, requestOptions) {
4623
+ const _headers = mergeHeaders(this._options?.headers, requestOptions?.headers);
4624
+ const _response = await fetcher({
4625
+ url: url_exports.join(
4626
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4627
+ "v1/auth/token/exchange"
4628
+ ),
4629
+ method: "POST",
4630
+ headers: _headers,
4631
+ contentType: "application/json",
4632
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4633
+ requestType: "json",
4634
+ body: request,
4635
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4636
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4637
+ abortSignal: requestOptions?.abortSignal,
4638
+ fetchFn: this._options?.fetch,
4639
+ logging: this._options.logging
4640
+ });
4641
+ if (_response.ok) {
4642
+ return { data: _response.body, rawResponse: _response.rawResponse };
4643
+ }
4644
+ if (_response.error.reason === "status-code") {
4645
+ switch (_response.error.statusCode) {
4646
+ case 400:
4647
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
4648
+ case 401:
4649
+ throw new UnauthorizedError(_response.error.body, _response.rawResponse);
4650
+ case 403:
4651
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4652
+ case 404:
4653
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4654
+ case 429:
4655
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4656
+ default:
4657
+ throw new VectrosError({
4658
+ statusCode: _response.error.statusCode,
4659
+ body: _response.error.body,
4660
+ rawResponse: _response.rawResponse
4661
+ });
4662
+ }
4663
+ }
4664
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/auth/token/exchange");
4665
+ }
4340
4666
  };
4341
4667
  var ComplianceClient = class {
4342
4668
  constructor(options) {
@@ -4604,7 +4930,7 @@ var DocumentsClient = class {
4604
4930
  * userId: "550e8400-e29b-41d4-a716-446655440000",
4605
4931
  * scope: "group:eng-team",
4606
4932
  * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
4607
- * startFrom: "doc_prev123"
4933
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
4608
4934
  * })
4609
4935
  */
4610
4936
  listDocuments(request = {}, requestOptions) {
@@ -4914,6 +5240,7 @@ var DocumentsClient = class {
4914
5240
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
4915
5241
  *
4916
5242
  * @throws {@link Vectros.BadRequestError}
5243
+ * @throws {@link Vectros.ForbiddenError}
4917
5244
  * @throws {@link Vectros.NotFoundError}
4918
5245
  * @throws {@link Vectros.ConflictError}
4919
5246
  * @throws {@link Vectros.TooManyRequestsError}
@@ -4961,6 +5288,8 @@ var DocumentsClient = class {
4961
5288
  switch (_response.error.statusCode) {
4962
5289
  case 400:
4963
5290
  throw new BadRequestError(_response.error.body, _response.rawResponse);
5291
+ case 403:
5292
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4964
5293
  case 404:
4965
5294
  throw new NotFoundError(_response.error.body, _response.rawResponse);
4966
5295
  case 409:
@@ -4991,7 +5320,7 @@ var DocumentsClient = class {
4991
5320
  * field: "po_number",
4992
5321
  * value: "PO-1001",
4993
5322
  * prefix: "PO-2024",
4994
- * startFrom: "550e8400-e29b-41d4-a716-446655440000",
5323
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl",
4995
5324
  * userId: "550e8400-e29b-41d4-a716-446655440000",
4996
5325
  * scope: "org:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
4997
5326
  * })
@@ -5367,7 +5696,7 @@ var FoldersClient = class {
5367
5696
  * parentFolderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
5368
5697
  * userId: "550e8400-e29b-41d4-a716-446655440000",
5369
5698
  * scope: "group:eng-team",
5370
- * startFrom: "fld_prev123"
5699
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
5371
5700
  * })
5372
5701
  */
5373
5702
  listFolders(request = {}, requestOptions) {
@@ -5544,6 +5873,7 @@ var FoldersClient = class {
5544
5873
  * @param {Vectros.UpdateFolderRequest} request
5545
5874
  * @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
5546
5875
  *
5876
+ * @throws {@link Vectros.ForbiddenError}
5547
5877
  * @throws {@link Vectros.NotFoundError}
5548
5878
  * @throws {@link Vectros.TooManyRequestsError}
5549
5879
  *
@@ -5588,6 +5918,8 @@ var FoldersClient = class {
5588
5918
  }
5589
5919
  if (_response.error.reason === "status-code") {
5590
5920
  switch (_response.error.statusCode) {
5921
+ case 403:
5922
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5591
5923
  case 404:
5592
5924
  throw new NotFoundError(_response.error.body, _response.rawResponse);
5593
5925
  case 429:
@@ -5670,6 +6002,7 @@ var FoldersClient = class {
5670
6002
  * @param {FoldersClient.RequestOptions} requestOptions - Request-specific configuration.
5671
6003
  *
5672
6004
  * @throws {@link Vectros.BadRequestError}
6005
+ * @throws {@link Vectros.ForbiddenError}
5673
6006
  * @throws {@link Vectros.NotFoundError}
5674
6007
  * @throws {@link Vectros.ConflictError}
5675
6008
  * @throws {@link Vectros.TooManyRequestsError}
@@ -5717,6 +6050,8 @@ var FoldersClient = class {
5717
6050
  switch (_response.error.statusCode) {
5718
6051
  case 400:
5719
6052
  throw new BadRequestError(_response.error.body, _response.rawResponse);
6053
+ case 403:
6054
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5720
6055
  case 404:
5721
6056
  throw new NotFoundError(_response.error.body, _response.rawResponse);
5722
6057
  case 409:
@@ -5804,7 +6139,8 @@ var IdentityClient = class {
5804
6139
  *
5805
6140
  * @example
5806
6141
  * await client.identity.listEntities({
5807
- * namespace: "team"
6142
+ * namespace: "team",
6143
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
5808
6144
  * })
5809
6145
  */
5810
6146
  listEntities(request, requestOptions) {
@@ -6568,7 +6904,7 @@ var IdentityClient = class {
6568
6904
  * @example
6569
6905
  * await client.identity.listUsers({
6570
6906
  * externalId: "usr_12345",
6571
- * startFrom: "550e8400-e29b-41d4-a716-446655440000",
6907
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl",
6572
6908
  * type: "person_v1",
6573
6909
  * field: "team",
6574
6910
  * value: "engineering",
@@ -6749,11 +7085,12 @@ var IdentityClient = class {
6749
7085
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users/{id}");
6750
7086
  }
6751
7087
  /**
6752
- * Updates mutable fields on an existing user (such as email, status, payload, or schema binding). The `type` field is immutable after creation. This endpoint also activates an invited user: a PUT that moves a PENDING user to ACTIVE and carries `inviteToken`, `externalSubject`, and `emailVerifiedAttestation=true` completes the invitation. Requires the `users:u` scope.
7088
+ * Updates mutable fields on an existing user (such as email, status, payload, or schema binding). The `type` field is immutable after creation, and `email` cannot be changed while an invitation to that user is still outstanding — revoke the invitation, or invite the new address instead. This endpoint also activates an invited user: a PUT that moves a PENDING user to ACTIVE and carries `inviteToken`, `externalSubject`, and `emailVerifiedAttestation=true` completes the invitation. Requires the `users:u` scope.
6753
7089
  *
6754
7090
  * @param {Vectros.UpdateUserRequest} request
6755
7091
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
6756
7092
  *
7093
+ * @throws {@link Vectros.BadRequestError}
6757
7094
  * @throws {@link Vectros.NotFoundError}
6758
7095
  * @throws {@link Vectros.TooManyRequestsError}
6759
7096
  *
@@ -6798,6 +7135,8 @@ var IdentityClient = class {
6798
7135
  }
6799
7136
  if (_response.error.reason === "status-code") {
6800
7137
  switch (_response.error.statusCode) {
7138
+ case 400:
7139
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
6801
7140
  case 404:
6802
7141
  throw new NotFoundError(_response.error.body, _response.rawResponse);
6803
7142
  case 429:
@@ -6931,6 +7270,69 @@ var IdentityClient = class {
6931
7270
  }
6932
7271
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/users/lookup");
6933
7272
  }
7273
+ /**
7274
+ * Answers "does a user with this email hold an ACTIVE access profile in this app context" — a narrow existence check, not a general lookup. `exists` is false for a member whose access to this context has been suspended, not only for a member who was never granted it. The answer is scoped to the `contextId` you supply: it does not reveal whether the email exists elsewhere in your tenant or account, only whether it belongs to an active member of the named context. Returns `{exists, userId, status}` — never the full user record — so a caller asking "does X exist" cannot receive that user's payload/schema binding/etc. as a side effect. Useful for resolving an email you were handed (for example, by an org-admin adding an existing member to another org) to a `userId`, without paging through the full context membership. Requires the `users:r` scope.
7275
+ *
7276
+ * @param {Vectros.UserExistsByEmailRequest} request
7277
+ * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
7278
+ *
7279
+ * @throws {@link Vectros.BadRequestError}
7280
+ * @throws {@link Vectros.ForbiddenError}
7281
+ *
7282
+ * @example
7283
+ * await client.identity.userExistsByEmail({
7284
+ * email: "user@example.com",
7285
+ * contextId: "myapp"
7286
+ * })
7287
+ */
7288
+ userExistsByEmail(request = {}, requestOptions) {
7289
+ return HttpResponsePromise.fromPromise(this.__userExistsByEmail(request, requestOptions));
7290
+ }
7291
+ async __userExistsByEmail(request = {}, requestOptions) {
7292
+ const { email, contextId } = request;
7293
+ const _queryParams = {
7294
+ email,
7295
+ contextId
7296
+ };
7297
+ const _authRequest = await this._options.authProvider.getAuthRequest();
7298
+ const _headers = mergeHeaders(
7299
+ _authRequest.headers,
7300
+ this._options?.headers,
7301
+ requestOptions?.headers
7302
+ );
7303
+ const _response = await fetcher({
7304
+ url: url_exports.join(
7305
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
7306
+ "v1/users/exists-by-email"
7307
+ ),
7308
+ method: "GET",
7309
+ headers: _headers,
7310
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
7311
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
7312
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
7313
+ abortSignal: requestOptions?.abortSignal,
7314
+ fetchFn: this._options?.fetch,
7315
+ logging: this._options.logging
7316
+ });
7317
+ if (_response.ok) {
7318
+ return { data: _response.body, rawResponse: _response.rawResponse };
7319
+ }
7320
+ if (_response.error.reason === "status-code") {
7321
+ switch (_response.error.statusCode) {
7322
+ case 400:
7323
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
7324
+ case 403:
7325
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
7326
+ default:
7327
+ throw new VectrosError({
7328
+ statusCode: _response.error.statusCode,
7329
+ body: _response.error.body,
7330
+ rawResponse: _response.rawResponse
7331
+ });
7332
+ }
7333
+ }
7334
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/users/exists-by-email");
7335
+ }
6934
7336
  /**
6935
7337
  * Returns the audit trail of changes to a user, most recent first. Identity history is always recorded and always available. Sensitive field values are redacted in every historical version. Returns a page in the `{data, nextCursor}` envelope. Requires the `users:r` scope.
6936
7338
  *
@@ -7445,7 +7847,7 @@ var RecordsClient = class {
7445
7847
  * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
7446
7848
  * userId: "550e8400-e29b-41d4-a716-446655440000",
7447
7849
  * scope: "group:eng-team",
7448
- * startFrom: "550e8400-e29b-41d4-a716-446655440000"
7850
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
7449
7851
  * })
7450
7852
  */
7451
7853
  listRecords(request = {}, requestOptions) {
@@ -7633,6 +8035,7 @@ var RecordsClient = class {
7633
8035
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
7634
8036
  *
7635
8037
  * @throws {@link Vectros.BadRequestError}
8038
+ * @throws {@link Vectros.ForbiddenError}
7636
8039
  * @throws {@link Vectros.NotFoundError}
7637
8040
  * @throws {@link Vectros.ConflictError}
7638
8041
  * @throws {@link Vectros.TooManyRequestsError}
@@ -7681,6 +8084,8 @@ var RecordsClient = class {
7681
8084
  switch (_response.error.statusCode) {
7682
8085
  case 400:
7683
8086
  throw new BadRequestError(_response.error.body, _response.rawResponse);
8087
+ case 403:
8088
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
7684
8089
  case 404:
7685
8090
  throw new NotFoundError(_response.error.body, _response.rawResponse);
7686
8091
  case 409:
@@ -7762,6 +8167,7 @@ var RecordsClient = class {
7762
8167
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
7763
8168
  *
7764
8169
  * @throws {@link Vectros.BadRequestError}
8170
+ * @throws {@link Vectros.ForbiddenError}
7765
8171
  * @throws {@link Vectros.NotFoundError}
7766
8172
  * @throws {@link Vectros.ConflictError}
7767
8173
  * @throws {@link Vectros.TooManyRequestsError}
@@ -7812,6 +8218,8 @@ var RecordsClient = class {
7812
8218
  switch (_response.error.statusCode) {
7813
8219
  case 400:
7814
8220
  throw new BadRequestError(_response.error.body, _response.rawResponse);
8221
+ case 403:
8222
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
7815
8223
  case 404:
7816
8224
  throw new NotFoundError(_response.error.body, _response.rawResponse);
7817
8225
  case 409:
@@ -7829,7 +8237,15 @@ var RecordsClient = class {
7829
8237
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "PATCH", "/v1/records/{id}");
7830
8238
  }
7831
8239
  /**
7832
- * Finds records by the value of a lookup field declared on the type's schema. Provide exactly one lookup mode: `value` (exact match), `from`+`to` (inclusive range, ascending by value), or `prefix` (string fields only, ascending). Range and prefix lookups are not supported on a sensitive field, because its value is stored as a blind index and has no sortable order. An exact-`value` lookup on a sensitive field is also rejected on this GET endpoint — the value must not appear in the URL — so use the `POST /v1/records/lookup` body variant for sensitive fields. Results are paginated: set `limit` for the page size and pass the returned `nextCursor` back as `startFrom` for the next page. Requires the `records:r:<type>` scope.
8240
+ * Finds records by the value of a lookup field declared on the type's schema. Provide exactly one lookup mode: `value` (exact match), `from`+`to` (inclusive range, ascending by value), or `prefix` (string fields only, ascending). Range and prefix lookups are not supported on a sensitive field, because its value is stored as a blind index and has no sortable order. An exact-`value` lookup on a sensitive field is also rejected on this GET endpoint — the value must not appear in the URL — so use the `POST /v1/records/lookup` body variant for sensitive fields.
8241
+ *
8242
+ * A `value` lookup can additionally be narrowed to a window of the lookup field's **sort key** using `sortFrom` and/or `sortTo` (inclusive) — for example, one session's records created since a timestamp. The sort key is whatever the schema declares as that lookup's `sortBy` (`createdAt` by default, `lastUpdated`, or another field), and bounds are given in that field's own units — epoch milliseconds for the two timestamp options. **Records that have no value for the sorted field are never included in a bounded window** — they are ordered ahead of every record that does have one, and a `sortFrom`/`sortTo` window only ever selects from records carrying a value. The sorted field does not have to be `required`.
8243
+ *
8244
+ * Narrowing is available on any lookup field your schema declares for fast equality lookup, and on `externalId` — which is always ordered by creation time, so its bounds are epoch milliseconds whatever the schema says. It is rejected (`400`) for a field declared with `rangeEnabled`, for a field declared beyond the schema's fast-lookup budget, for a lookup whose `sortBy` names a sensitive field (a sensitive value is stored as a blind index and has no order), and for a window whose start is after its end. Ownership fields are not lookup fields on this endpoint at all — see the `field` parameter.
8245
+ *
8246
+ * While paging a narrowed lookup, keep every other parameter identical. The cursor is valid only for the exact query that returned it — changing `order`, `sortFrom`, `sortTo`, or dropping them altogether, is rejected rather than silently resumed at a position that means something different in the new query.
8247
+ *
8248
+ * Results are paginated: set `limit` for the page size and pass the returned `nextCursor` back as `startFrom` for the next page. **Keep paging until `nextCursor` is null** — a page can come back empty or shorter than `limit` while more results remain, so an empty page is not the end of the results. Requires the `records:r:<type>` scope.
7833
8249
  *
7834
8250
  * @param {Vectros.LookupRecordsRequest} request
7835
8251
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -7841,22 +8257,40 @@ var RecordsClient = class {
7841
8257
  * type: "intake_form",
7842
8258
  * field: "email",
7843
8259
  * value: "jane@example.com",
8260
+ * values: ["open"],
7844
8261
  * prefix: "jane",
7845
- * startFrom: "550e8400-e29b-41d4-a716-446655440000"
8262
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
7846
8263
  * })
7847
8264
  */
7848
8265
  lookupRecords(request, requestOptions) {
7849
8266
  return HttpResponsePromise.fromPromise(this.__lookupRecords(request, requestOptions));
7850
8267
  }
7851
8268
  async __lookupRecords(request, requestOptions) {
7852
- const { type: type_, field, value, from: from_, to, prefix, startFrom, limit, includePayload, order } = request;
8269
+ const {
8270
+ type: type_,
8271
+ field,
8272
+ value,
8273
+ values,
8274
+ from: from_,
8275
+ to,
8276
+ prefix,
8277
+ sortFrom,
8278
+ sortTo,
8279
+ startFrom,
8280
+ limit,
8281
+ includePayload,
8282
+ order
8283
+ } = request;
7853
8284
  const _queryParams = {
7854
8285
  type: type_,
7855
8286
  field,
7856
8287
  value,
8288
+ values,
7857
8289
  from: from_,
7858
8290
  to,
7859
8291
  prefix,
8292
+ sortFrom,
8293
+ sortTo,
7860
8294
  startFrom,
7861
8295
  limit,
7862
8296
  includePayload,
@@ -7900,7 +8334,7 @@ var RecordsClient = class {
7900
8334
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/records/lookup");
7901
8335
  }
7902
8336
  /**
7903
- * Body-based equivalent of `GET /v1/records/lookup`. Use this when looking up by a sensitive field: the value travels in the request body (and is blind-indexed server-side) instead of in the URL query string, so it never lands in access, CDN, or proxy logs. The GET variant rejects an exact-value lookup on a sensitive field and directs you here. Non-sensitive exact-value, range (`from`+`to`), and prefix lookups also work here. Returns the same `{data, nextCursor}` envelope and uses the same pagination as the GET variant. Requires the `records:r:<type>` scope.
8337
+ * Body-based equivalent of `GET /v1/records/lookup`. Use this when looking up by a sensitive field: the value travels in the request body (and is blind-indexed server-side) instead of in the URL query string, so it never lands in access, CDN, or proxy logs. The GET variant rejects an exact-value lookup on a sensitive field and directs you here. Non-sensitive exact-value, range (`from`+`to`), and prefix lookups also work here, as does narrowing a `value` lookup by the field's sort key with `sortFrom`/`sortTo` — see the GET variant for what the sort key is and when narrowing by it is available. Returns the same `{data, nextCursor}` envelope and uses the same pagination as the GET variant; keep paging until `nextCursor` is null rather than stopping on an empty page. Requires the `records:r:<type>` scope.
7904
8338
  *
7905
8339
  * @param {Vectros.RecordLookupRequest} request
7906
8340
  * @param {RecordsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -8095,7 +8529,7 @@ var SchemasClient = class {
8095
8529
  * scope: "org:6ba7b810-9dad-11d1-80b4-00c04fd430c8",
8096
8530
  * surface: "document",
8097
8531
  * recordType: "intake_form",
8098
- * startFrom: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
8532
+ * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
8099
8533
  * })
8100
8534
  */
8101
8535
  listSchemas(request = {}, requestOptions) {
@@ -8299,6 +8733,7 @@ var SchemasClient = class {
8299
8733
  * @param {SchemasClient.RequestOptions} requestOptions - Request-specific configuration.
8300
8734
  *
8301
8735
  * @throws {@link Vectros.BadRequestError}
8736
+ * @throws {@link Vectros.ForbiddenError}
8302
8737
  * @throws {@link Vectros.NotFoundError}
8303
8738
  * @throws {@link Vectros.TooManyRequestsError}
8304
8739
  *
@@ -8347,6 +8782,8 @@ var SchemasClient = class {
8347
8782
  switch (_response.error.statusCode) {
8348
8783
  case 400:
8349
8784
  throw new BadRequestError(_response.error.body, _response.rawResponse);
8785
+ case 403:
8786
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
8350
8787
  case 404:
8351
8788
  throw new NotFoundError(_response.error.body, _response.rawResponse);
8352
8789
  case 429:
@@ -8652,15 +9089,15 @@ function warnOnSuboptimalKey(key, log) {
8652
9089
  } else if (key.prefix === "st") {
8653
9090
  log.warn(
8654
9091
  { prefix: key.prefix, env: key.env },
8655
- "short-lived token (st_*) will expire mid-session (1h default, 24h max). For long-running MCP installs, prefer a long-lived ssk_* key."
9092
+ "short-lived token (st_*) will expire mid-session (1h max). For long-running MCP installs, prefer a long-lived ssk_* key."
8656
9093
  );
8657
9094
  }
8658
9095
  }
8659
9096
 
8660
9097
  // src/build-info.ts
8661
9098
  var BUILD_INFO = {
8662
- mcpServer: true ? "0.12.0" : "dev",
8663
- sdk: true ? "0.37.0" : "dev"
9099
+ mcpServer: true ? "0.14.0" : "dev",
9100
+ sdk: true ? "0.39.0" : "dev"
8664
9101
  };
8665
9102
 
8666
9103
  // src/log.ts
@@ -8700,7 +9137,9 @@ async function resolveIdentity({
8700
9137
  const derived = {
8701
9138
  status: "ok",
8702
9139
  environment: deriveEnvironment(environment),
8703
- principalType: derivePrincipalType(apiKey)
9140
+ principalType: derivePrincipalType(apiKey),
9141
+ mcpServerVersion: BUILD_INFO.mcpServer,
9142
+ sdkVersion: BUILD_INFO.sdk
8704
9143
  };
8705
9144
  if (!apiKey || !environment) {
8706
9145
  log.debug({ component: "identity", mode: "derived-only" }, "identity derived (no fetch)");
@@ -8728,7 +9167,13 @@ async function resolveIdentity({
8728
9167
  );
8729
9168
  }
8730
9169
  }
8731
- const merged = { ...derived, ...extended, status: "ok" };
9170
+ const merged = {
9171
+ ...derived,
9172
+ ...extended,
9173
+ status: "ok",
9174
+ mcpServerVersion: derived.mcpServerVersion,
9175
+ sdkVersion: derived.sdkVersion
9176
+ };
8732
9177
  log.debug(
8733
9178
  {
8734
9179
  component: "identity",
@@ -9050,14 +9495,23 @@ var import_zod2 = require("zod");
9050
9495
  function pageItems(page) {
9051
9496
  return page.data ?? [];
9052
9497
  }
9498
+ var MAX_PAGES = 100;
9499
+ var MAX_ROWS = 1e6;
9053
9500
  async function drainPages(fetchPage) {
9054
9501
  const all = [];
9055
9502
  let startFrom;
9503
+ let pages = 0;
9056
9504
  for (; ; ) {
9057
9505
  const page = await fetchPage(startFrom);
9058
- if (page.data?.length) all.push(...page.data);
9506
+ pages++;
9507
+ if (page.data?.length) for (const item of page.data) all.push(item);
9059
9508
  const next = page.nextCursor;
9060
9509
  if (!next) break;
9510
+ if (pages > MAX_PAGES || all.length > MAX_ROWS) {
9511
+ throw new Error(
9512
+ `drainPages: exceeded the runaway guard (${all.length} rows over ${pages} pages, limits ${MAX_ROWS}/${MAX_PAGES}) without the cursor going null. Refusing to return a partial result \u2014 the backend may be returning empty pages with a live cursor.`
9513
+ );
9514
+ }
9061
9515
  startFrom = next;
9062
9516
  }
9063
9517
  return all;
@@ -9068,14 +9522,23 @@ var MCP_DEFAULT_LIMIT2 = 3;
9068
9522
  var MCP_MAX_LIMIT2 = 100;
9069
9523
  var inputSchema2 = {
9070
9524
  type: import_zod2.z.string().min(1, "type (record type) is required").describe('Record type / schema name (e.g. "patient", "clinical_note").'),
9071
- // Lookup-mode args — provide `field` plus EXACTLY ONE of: value | from+to | prefix.
9525
+ // Lookup-mode args — provide `field` plus EXACTLY ONE of: value | values | from+to | prefix.
9072
9526
  field: import_zod2.z.string().optional().describe(
9073
- 'Lookup mode: name of the lookup-indexed field to query by. `externalId` is always queryable \u2014 e.g. `{type:"control", field:"externalId", value:"ctrl-scoped-key"}` finds the record you created under that externalId. (record_get / record_update also accept externalId directly.)'
9527
+ 'Lookup mode: name of the lookup-indexed field to query by. `externalId` is always queryable \u2014 e.g. `{type:"control", field:"externalId", value:"ctrl-scoped-key"}` finds the record you created under that externalId. (record_get / record_update also accept externalId directly.) For a SCHEMA-declared composite lookup \u2014 check list_schemas\' lookupFields for a fieldNames entry, not any two fields you pick \u2014 give those field names comma-joined in the declared order (e.g. "status,area"). Use `values` to match on several of those fields at once, or `value` to match on just the FIRST field named (equivalent to a one-element `values`); range/prefix are not valid on a composite field.'
9528
+ ),
9529
+ value: import_zod2.z.string().optional().describe(
9530
+ "Lookup mode (equality): exact-match value for `field`. Also valid when `field` names a SCHEMA-declared composite lookup \u2014 matches just the first named field, grouping by the rest (equivalent to `values` with one entry)."
9531
+ ),
9532
+ values: import_zod2.z.array(import_zod2.z.string()).min(1).max(3).optional().describe(
9533
+ 'Lookup mode (composite equality): only valid against a lookup the SCHEMA declares over several fields together (a fieldNames entry in list_schemas \u2014 you cannot combine two ordinary single-field lookups this way). One value per field, in the order the schema declares them (e.g. field="status,area", values=["open","billing"]). Mutually exclusive with `value`; a single-element array is exactly `value` written uniformly. Supplying FEWER values than the lookup declares is allowed \u2014 as long as they\'re a leading run of its fields \u2014 and returns records grouped by the fields left unspecified; `order` then sorts within each group, not across them. Record lookups only (not documents/users/entities).'
9074
9534
  ),
9075
- value: import_zod2.z.string().optional().describe("Lookup mode (equality): exact-match value for `field`."),
9076
9535
  from: import_zod2.z.string().optional().describe("Lookup mode (range): inclusive lower bound; requires `to`. Non-sensitive fields only."),
9077
9536
  to: import_zod2.z.string().optional().describe("Lookup mode (range): inclusive upper bound; requires `from`."),
9078
9537
  prefix: import_zod2.z.string().optional().describe("Lookup mode (prefix): match values starting with this. String, non-sensitive fields only."),
9538
+ sortFrom: import_zod2.z.string().optional().describe(
9539
+ "Lookup mode (equality/composite only): inclusive lower bound on the lookup field's sort key, narrowing a `value`/`values` match to a window instead of paging the whole match. Give the bound in the sort field's own units (epoch milliseconds for `createdAt`/`lastUpdated`). With `values`, requires every value be supplied (no grouping) for one continuous ordering. Not valid with `from`/`to`/`prefix`."
9540
+ ),
9541
+ sortTo: import_zod2.z.string().optional().describe("Lookup mode (equality/composite only): inclusive upper bound on the sort key; pairs with `sortFrom`."),
9079
9542
  order: import_zod2.z.enum(["asc", "desc"]).optional().describe(
9080
9543
  "Lookup mode: sort direction by the looked-up field. `asc` (default) or `desc` \u2014 use `desc` with a range/prefix lookup to get the most-recent / highest values first (e.g. latest-N). Ignored in list mode."
9081
9544
  ),
@@ -9092,54 +9555,106 @@ var inputSchema2 = {
9092
9555
  var recordQuery = ({ client, log }) => ({
9093
9556
  name: "record_query",
9094
9557
  title: "Record query (list or lookup)",
9095
- description: "Query structured records of a given type. Two modes:\n \u2022 Lookup on a lookup-indexed field: pass `field` plus exactly one of `value` (exact), `from`+`to` (range), or `prefix`. Works on sensitive fields too.\n \u2022 List by type: omit `field`; optionally filter by `userId` or `scope` (`namespace:value`).\nReturns a `{ data, nextCursor }` page: `data` holds up to `limit` records (default 3, max 100 \u2014 raise it in one call when you can accept the payload), and a non-null `nextCursor` means more remain \u2014 pass it back as `startFrom` to page. Mode is auto-detected from the arguments present.",
9558
+ description: "Query structured records of a given type. Two modes:\n \u2022 Lookup on a lookup-indexed field: pass `field` plus exactly one of `value` (exact), `values` (exact, composite \u2014 one value per field; a single-element array is the same as `value`), `from`+`to` (range), or `prefix`. `values` with more than one entry, or a comma-joined `field`, ONLY works for a lookup the schema itself declared over several fields together \u2014 check list_schemas' lookupFields for a fieldNames entry, then pass those field names comma-joined as `field`; `from`/`to`/`prefix` are not valid there. Works on sensitive fields too. Optionally narrow an exact match to a window with `sortFrom`/`sortTo`.\n \u2022 List by type: omit `field`; optionally filter by `userId` or `scope` (`namespace:value`).\nReturns a `{ data, nextCursor }` page: `data` holds up to `limit` records (default 3, max 100 \u2014 raise it in one call when you can accept the payload), and a non-null `nextCursor` means more remain \u2014 pass it back as `startFrom` to page. A composite lookup given fewer than its full field count returns records grouped by the unspecified fields, ordered within each group. Mode is auto-detected from the arguments present.",
9096
9559
  inputSchema: inputSchema2,
9097
9560
  handler: async (args) => {
9098
9561
  const limit = args.limit ?? MCP_DEFAULT_LIMIT2;
9099
9562
  const type = args.type;
9100
9563
  const field = args.field;
9101
9564
  const value = args.value;
9565
+ const values = args.values;
9102
9566
  const from = args.from;
9103
9567
  const to = args.to;
9104
9568
  const prefix = args.prefix;
9569
+ const sortFrom = args.sortFrom;
9570
+ const sortTo = args.sortTo;
9105
9571
  const startFrom = args.startFrom;
9572
+ if (!field && (value !== void 0 || values !== void 0 || from !== void 0 || to !== void 0 || prefix !== void 0 || sortFrom !== void 0 || sortTo !== void 0)) {
9573
+ return toolError(
9574
+ "record_query",
9575
+ new Error(
9576
+ "'value'/'values'/'from'/'to'/'prefix'/'sortFrom'/'sortTo' require 'field' \u2014 without it, list mode runs and silently ignores them. Pass 'field' to run a lookup, or drop these to list by type."
9577
+ )
9578
+ );
9579
+ }
9106
9580
  try {
9107
9581
  let page;
9108
9582
  if (field) {
9583
+ const fieldNames = field.split(",").map((f) => f.trim()).filter(Boolean);
9584
+ const normalizedField = fieldNames.join(",");
9585
+ const isCompositeField = fieldNames.length > 1;
9109
9586
  const hasEquality = value !== void 0;
9587
+ const hasComposite = values !== void 0;
9110
9588
  const hasRange = from !== void 0 || to !== void 0;
9111
9589
  const hasPrefix = prefix !== void 0;
9112
- const modes = Number(hasEquality) + Number(hasRange) + Number(hasPrefix);
9590
+ const modes = Number(hasEquality) + Number(hasComposite) + Number(hasRange) + Number(hasPrefix);
9113
9591
  if (modes === 0) {
9114
9592
  return toolError(
9115
9593
  "record_query",
9116
9594
  new Error(
9117
- `lookup on field '${field}' needs one of: 'value' (exact), 'from'+'to' (range), or 'prefix'.`
9595
+ `lookup on field '${field}' needs one of: 'value' (exact), 'values' (composite exact), 'from'+'to' (range), or 'prefix'.`
9118
9596
  )
9119
9597
  );
9120
9598
  }
9121
9599
  if (modes > 1) {
9122
9600
  return toolError(
9123
9601
  "record_query",
9124
- new Error("'value', 'from'/'to', and 'prefix' are mutually exclusive \u2014 provide exactly one.")
9602
+ new Error(
9603
+ "'value', 'values', 'from'/'to', and 'prefix' are mutually exclusive \u2014 provide exactly one."
9604
+ )
9125
9605
  );
9126
9606
  }
9127
9607
  if (hasRange && !(from !== void 0 && to !== void 0)) {
9128
9608
  return toolError("record_query", new Error("range lookup requires both 'from' and 'to'."));
9129
9609
  }
9610
+ if (isCompositeField && (hasRange || hasPrefix)) {
9611
+ return toolError(
9612
+ "record_query",
9613
+ new Error(
9614
+ `'field' names ${fieldNames.length} fields ('${normalizedField}') \u2014 a composite lookup is exact-match only ('value' or 'values'), not 'from'/'to'/'prefix'.`
9615
+ )
9616
+ );
9617
+ }
9618
+ if (hasComposite && values.length > fieldNames.length) {
9619
+ return toolError(
9620
+ "record_query",
9621
+ new Error(
9622
+ `'values' has ${values.length} entries but 'field' names only ${fieldNames.length} field(s) ('${normalizedField}') \u2014 supply at most one value per field, in order (fewer only as a leading run).`
9623
+ )
9624
+ );
9625
+ }
9626
+ if (sortFrom !== void 0 || sortTo !== void 0) {
9627
+ if (!(hasEquality || hasComposite)) {
9628
+ return toolError(
9629
+ "record_query",
9630
+ new Error("'sortFrom'/'sortTo' narrow a 'value' or 'values' match only \u2014 not 'from'/'to'/'prefix'.")
9631
+ );
9632
+ }
9633
+ if (hasComposite && values.length < fieldNames.length) {
9634
+ return toolError(
9635
+ "record_query",
9636
+ new Error(
9637
+ `'sortFrom'/'sortTo' with 'values' requires a value for every field named in 'field' (got ${values.length} of ${fieldNames.length}) \u2014 a partial/grouped match has no single continuous ordering to window.`
9638
+ )
9639
+ );
9640
+ }
9641
+ }
9130
9642
  page = await client.records.lookupRecordsByBody({
9131
9643
  type,
9132
- field,
9644
+ field: normalizedField,
9133
9645
  value,
9646
+ values,
9134
9647
  from,
9135
9648
  to,
9136
9649
  prefix,
9650
+ sortFrom,
9651
+ sortTo,
9137
9652
  order: args.order,
9138
9653
  startFrom,
9139
9654
  limit
9140
9655
  });
9141
9656
  log.debug(
9142
- { tool: "record_query", mode: "lookup", type, field, returned: pageItems(page).length },
9657
+ { tool: "record_query", mode: "lookup", type, field: normalizedField, returned: pageItems(page).length },
9143
9658
  "record_query lookup ok"
9144
9659
  );
9145
9660
  } else {
@@ -10078,7 +10593,7 @@ var inputSchema18 = {
10078
10593
  var currentIdentity = ({ log, apiKey, environment }) => ({
10079
10594
  name: "current_identity",
10080
10595
  title: "Current identity (tenant + principal scope)",
10081
- description: "Describe the credential the MCP server is operating under. Returns tenantId, environment (staging|production), principalType (root_key|scoped_key|token), principalKeyId, principalLabel, and (for scoped credentials) allowedActions plus dataScope \u2014 the credential's reach as { userId, scopes[] }, where each scope is a `namespace:value` string (e.g. \"org:<uuid>\"). Use this when the user asks 'what can you do here?' or 'what tenant am I in?'. Calls GET /v1/ping under the hood.",
10596
+ description: "Describe the credential the MCP server is operating under. Returns tenantId, environment (staging|production), principalType (root_key|scoped_key|token), principalKeyId, principalLabel, and (for scoped credentials) allowedActions plus dataScope \u2014 the credential's reach as { userId, scopes[] }, where each scope is a `namespace:value` string (e.g. \"org:<uuid>\"). Also reports mcpServerVersion (this server's own package version) and sdkVersion (the bundled @vectros-ai/sdk version) so a caller can tell what build it's talking to. Use this when the user asks 'what can you do here?', 'what tenant am I in?', or 'what version is this?'. Calls GET /v1/ping under the hood.",
10082
10597
  inputSchema: inputSchema18,
10083
10598
  handler: async () => {
10084
10599
  try {
@@ -10556,7 +11071,7 @@ var identityResource = ({ log, apiKey, environment }) => ({
10556
11071
  name: "identity",
10557
11072
  uri: "vectros://identity",
10558
11073
  title: "Current identity (tenant + principal scope)",
10559
- description: "The tenant + principal scope the MCP server is authenticated as. Returns the same payload as the `current_identity` tool: status, tenantId, environment, principalType, principalKeyId, principalLabel, and (for scoped credentials) allowedActions + dataScope ({ userId, scopes[] }). The /v1/ping backing call ships this extended shape today.",
11074
+ description: "The tenant + principal scope the MCP server is authenticated as. Returns the same payload as the `current_identity` tool: status, tenantId, environment, principalType, principalKeyId, principalLabel, (for scoped credentials) allowedActions + dataScope ({ userId, scopes[] }), plus mcpServerVersion and sdkVersion (this build's own version and the bundled @vectros-ai/sdk version). The /v1/ping backing call ships the extended shape today; the version fields are always client-derived.",
10560
11075
  mimeType: "application/json",
10561
11076
  read: async () => {
10562
11077
  const identity = await resolveIdentity({ log, apiKey, environment });