qati-sdk 1.0.4 → 1.0.5
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 +144 -8
- package/dist/index.cjs +105 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -1
- package/dist/index.d.ts +85 -1
- package/dist/index.js +105 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@ The main feature groups on the client are:
|
|
|
13
13
|
- `client.advisory` — list persisted advisories for the tenant (with filters and pagination).
|
|
14
14
|
- `client.explain` — fetch a composite explain / attribution payload for a single entity (by entity key).
|
|
15
15
|
- `client.events` — send telemetry events to the ingestion pipeline.
|
|
16
|
+
- `client.webhooks` — manage webhook endpoints for a tenant (register, list, update, delete, trigger test delivery).
|
|
17
|
+
- `client.quotas` — read and configure numeric caps (entities, monthly events, rate) for a tenant.
|
|
16
18
|
|
|
17
19
|
No network traffic occurs until you call a method. Opening a client only prepares HTTP connections and resolves configuration.
|
|
18
20
|
|
|
@@ -166,6 +168,138 @@ try {
|
|
|
166
168
|
}
|
|
167
169
|
```
|
|
168
170
|
|
|
171
|
+
## Webhooks
|
|
172
|
+
|
|
173
|
+
`client.webhooks` manages webhook endpoints for a specific tenant. All methods require `tenantId` as their first argument — resolve it once with `client.tenant.verifyCredentials()` and reuse the value.
|
|
174
|
+
|
|
175
|
+
Supported event types: `trust_state.tier_escalated`, `advisory.created`.
|
|
176
|
+
|
|
177
|
+
### Register a webhook
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { Session } from 'qati-sdk';
|
|
181
|
+
|
|
182
|
+
const session = new Session();
|
|
183
|
+
const client = session.createClient();
|
|
184
|
+
try {
|
|
185
|
+
const creds = await client.tenant.verifyCredentials();
|
|
186
|
+
const webhook = await client.webhooks.create(creds.tenant_id, {
|
|
187
|
+
url: 'https://your-service.example.com/qati-events',
|
|
188
|
+
subscribed_event_types: ['trust_state.tier_escalated', 'advisory.created'],
|
|
189
|
+
enabled: true,
|
|
190
|
+
});
|
|
191
|
+
// webhook.secret is returned exactly once — store it for HMAC verification.
|
|
192
|
+
console.log(webhook.id, webhook.secret);
|
|
193
|
+
} finally {
|
|
194
|
+
await client.close();
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The `secret` field in the response is shown **only on creation**. Store it securely; you cannot retrieve it again. To rotate the secret, use `patch` with `rotate_secret: true`.
|
|
199
|
+
|
|
200
|
+
### List webhooks
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
const hooks = await client.webhooks.list(tenantId);
|
|
204
|
+
for (const h of hooks) {
|
|
205
|
+
console.log(h.id, h.url, h.enabled, h.consecutive_failures);
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Update a webhook
|
|
210
|
+
|
|
211
|
+
Any field is optional. Pass `rotate_secret: true` to get a new signing secret (returned in `new_secret`).
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const updated = await client.webhooks.patch(tenantId, webhookId, {
|
|
215
|
+
enabled: false,
|
|
216
|
+
subscribed_event_types: ['advisory.created'],
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Trigger a test delivery
|
|
221
|
+
|
|
222
|
+
Enqueues a synthetic event to the webhook. Useful for verifying your receiver end-to-end.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
const { delivery_id } = await client.webhooks.test(tenantId, webhookId);
|
|
226
|
+
console.log('test delivery queued:', delivery_id);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Delete a webhook
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
await client.webhooks.delete(tenantId, webhookId);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Verifying signatures
|
|
236
|
+
|
|
237
|
+
The platform signs every delivery with HMAC-SHA256. The signature is in the `X-QATI-Signature` header as `v1=<hex>`. The signed message is `"<unix_timestamp>.<raw_utf8_body>"` where the timestamp comes from `X-QATI-Timestamp`.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import crypto from 'crypto';
|
|
241
|
+
|
|
242
|
+
function verifySignature(
|
|
243
|
+
rawBody: string,
|
|
244
|
+
timestamp: string,
|
|
245
|
+
signature: string,
|
|
246
|
+
secret: string,
|
|
247
|
+
): boolean {
|
|
248
|
+
const expected =
|
|
249
|
+
'v1=' +
|
|
250
|
+
crypto
|
|
251
|
+
.createHmac('sha256', secret)
|
|
252
|
+
.update(`${timestamp}.${rawBody}`, 'utf8')
|
|
253
|
+
.digest('hex');
|
|
254
|
+
if (signature.length !== expected.length) return false;
|
|
255
|
+
return crypto.timingSafeEqual(
|
|
256
|
+
Buffer.from(signature, 'utf8'),
|
|
257
|
+
Buffer.from(expected, 'utf8'),
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Use `express.text({ type: 'application/json' })` (not `express.json()`) to capture the raw body string before parsing, so the HMAC is computed over the exact bytes the platform signed.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Quotas
|
|
267
|
+
|
|
268
|
+
`client.quotas` reads and writes the numeric caps that govern how much a tenant can send and store.
|
|
269
|
+
|
|
270
|
+
### Get current quotas
|
|
271
|
+
|
|
272
|
+
Returns the active limits; throws `QatiNotFoundError` (404) if no quota has been configured for the tenant yet.
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { Session } from 'qati-sdk';
|
|
276
|
+
|
|
277
|
+
const session = new Session();
|
|
278
|
+
const client = session.createClient();
|
|
279
|
+
try {
|
|
280
|
+
const creds = await client.tenant.verifyCredentials();
|
|
281
|
+
const quota = await client.quotas.get(creds.tenant_id);
|
|
282
|
+
console.log(quota.max_tracked_entities, quota.max_monthly_events, quota.max_events_per_second);
|
|
283
|
+
} finally {
|
|
284
|
+
await client.close();
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Upsert quotas
|
|
289
|
+
|
|
290
|
+
Creates or fully replaces the quota configuration. All three fields are required.
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
const quota = await client.quotas.upsert(tenantId, {
|
|
294
|
+
max_tracked_entities: 50000,
|
|
295
|
+
max_monthly_events: 5_000_000,
|
|
296
|
+
max_events_per_second: 1000,
|
|
297
|
+
});
|
|
298
|
+
console.log(quota.id, quota.tenant_id);
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
169
303
|
## Step 3 Configure the session
|
|
170
304
|
|
|
171
305
|
For most teams, environment variables are the simplest approach — set them once and the SDK picks them up automatically (via `dotenv` loading `.env` from the working directory when using `new Session()` / `resolveQatiConfig()`). If you need finer control (custom URLs, timeouts, or multiple tenants), pass a config object into `Session`.
|
|
@@ -540,13 +674,15 @@ Transient failures are retried automatically for **`POST /v1/events:batch`** acc
|
|
|
540
674
|
|
|
541
675
|
## Appendix: API surface (compact)
|
|
542
676
|
|
|
543
|
-
| Symbol
|
|
544
|
-
|
|
|
545
|
-
| `Session`
|
|
546
|
-
| `Client`
|
|
547
|
-
| `client.events`
|
|
548
|
-
| `
|
|
549
|
-
|
|
|
550
|
-
|
|
|
677
|
+
| Symbol | Role |
|
|
678
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
679
|
+
| `Session` | `new Session(config?)`, `session.config`, `session.createClient(httpClients?)`. |
|
|
680
|
+
| `Client` | Namespaces: `tenant`, `trustState`, `advisory`, `explain`, `events`, `webhooks`, `quotas`; `await client.close()`. |
|
|
681
|
+
| `client.events` | `enqueue`, `flush`, `shutdown`, `pendingCount`, `onIngestionFailure`. |
|
|
682
|
+
| `client.webhooks` | `list(tenantId)`, `create(tenantId, body)`, `patch(tenantId, webhookId, body)`, `delete(tenantId, webhookId)`, `test(tenantId, webhookId)`. |
|
|
683
|
+
| `client.quotas` | `get(tenantId)`, `upsert(tenantId, body)`. |
|
|
684
|
+
| `create*Event` | `createTransactionEvent`, `createAuthEvent`, `createModelOutputEvent`, `createSystemTelemetryEvent`, `createAnomalyFlagEvent`, `createBehaviorEvent`, `createNetworkEvent` — all exported from `qati-sdk`. |
|
|
685
|
+
| Config | `resolveQatiConfig`, `parseQatiConfig`, `baseUrlFor`, types `QatiConfigInput` / `QatiConfigOutput`. |
|
|
686
|
+
| Errors | `QatiSDKError`, `QatiAPIError`, `QatiAuthError`, `QatiNotFoundError`, `QatiRateLimitError`, `QatiServerError`, `QatiConfigError`. |
|
|
551
687
|
|
|
552
688
|
`HttpClient.request(...)` exists for advanced use; prefer resource methods for application code.
|
package/dist/index.cjs
CHANGED
|
@@ -78,19 +78,6 @@ var QatiConfigError = class extends QatiSDKError {
|
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
// src/config.ts
|
|
81
|
-
var EV_VARIABLES_NAMES = [
|
|
82
|
-
"QATI_TENANT_API_KEY",
|
|
83
|
-
"QATI_QUERY_API_BASE_URL",
|
|
84
|
-
"QATI_INGESTION_API_BASE_URL",
|
|
85
|
-
"QATI_TIMEOUT",
|
|
86
|
-
"QATI_MAX_RETRIES",
|
|
87
|
-
"QATI_INGESTION_BATCH_SIZE",
|
|
88
|
-
"QATI_INGESTION_FLUSH_INTERVAL_SECONDS",
|
|
89
|
-
"QATI_RETRY_BACKOFF_INITIAL_SECONDS",
|
|
90
|
-
"QATI_RETRY_BACKOFF_MAX_SECONDS",
|
|
91
|
-
"QATI_RETRY_JITTER_FRACTION",
|
|
92
|
-
"QATI_MAX_RETRIES"
|
|
93
|
-
];
|
|
94
81
|
var API_REGISTRY = {
|
|
95
82
|
query_api: "queryApiBaseUrl",
|
|
96
83
|
ingestion_api: "ingestionApiBaseUrl"
|
|
@@ -115,14 +102,22 @@ var stripUndefined = (obj) => {
|
|
|
115
102
|
};
|
|
116
103
|
var readEnvOverrides = () => {
|
|
117
104
|
const e = process.env;
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
105
|
+
const num = (key) => {
|
|
106
|
+
const v = e[key];
|
|
107
|
+
return v !== void 0 ? Number(v) : void 0;
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
tenantApiKey: e["QATI_TENANT_API_KEY"],
|
|
111
|
+
queryApiBaseUrl: e["QATI_QUERY_API_BASE_URL"],
|
|
112
|
+
ingestionApiBaseUrl: e["QATI_INGESTION_API_BASE_URL"],
|
|
113
|
+
timeout: num("QATI_TIMEOUT"),
|
|
114
|
+
maxRetries: num("QATI_MAX_RETRIES"),
|
|
115
|
+
ingestionBatchSize: num("QATI_INGESTION_BATCH_SIZE"),
|
|
116
|
+
ingestionFlushIntervalSeconds: num("QATI_INGESTION_FLUSH_INTERVAL_SECONDS"),
|
|
117
|
+
retryBackoffInitialSeconds: num("QATI_RETRY_BACKOFF_INITIAL_SECONDS"),
|
|
118
|
+
retryBackoffMaxSeconds: num("QATI_RETRY_BACKOFF_MAX_SECONDS"),
|
|
119
|
+
retryJitterFraction: num("QATI_RETRY_JITTER_FRACTION")
|
|
120
|
+
};
|
|
126
121
|
};
|
|
127
122
|
var resolveQatiConfig = (input) => {
|
|
128
123
|
dotenv.config({ path: ".env" });
|
|
@@ -607,6 +602,29 @@ var ExplainResource = class {
|
|
|
607
602
|
}
|
|
608
603
|
};
|
|
609
604
|
|
|
605
|
+
// src/resources/quota-resource.ts
|
|
606
|
+
var QuotaResource = class {
|
|
607
|
+
constructor(_http) {
|
|
608
|
+
this._http = _http;
|
|
609
|
+
}
|
|
610
|
+
_http;
|
|
611
|
+
apiName = "query_api";
|
|
612
|
+
async get(tenantId) {
|
|
613
|
+
return this._http.request(
|
|
614
|
+
"GET",
|
|
615
|
+
`/v1/tenants/${encodeURIComponent(tenantId)}/quotas`,
|
|
616
|
+
{ api_name: this.apiName }
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
async upsert(tenantId, body) {
|
|
620
|
+
return this._http.request(
|
|
621
|
+
"PUT",
|
|
622
|
+
`/v1/tenants/${encodeURIComponent(tenantId)}/quotas`,
|
|
623
|
+
{ api_name: this.apiName, data: body }
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
610
628
|
// src/resources/tenant-resource.ts
|
|
611
629
|
var TenantResource = class {
|
|
612
630
|
constructor(_http) {
|
|
@@ -680,6 +698,50 @@ var TrustStateResource = class {
|
|
|
680
698
|
}
|
|
681
699
|
};
|
|
682
700
|
|
|
701
|
+
// src/resources/webhook-resource.ts
|
|
702
|
+
var WebhookResource = class {
|
|
703
|
+
constructor(_http) {
|
|
704
|
+
this._http = _http;
|
|
705
|
+
}
|
|
706
|
+
_http;
|
|
707
|
+
apiName = "query_api";
|
|
708
|
+
async list(tenantId) {
|
|
709
|
+
return this._http.request(
|
|
710
|
+
"GET",
|
|
711
|
+
`/v1/tenants/${encodeURIComponent(tenantId)}/webhooks`,
|
|
712
|
+
{ api_name: this.apiName }
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
async create(tenantId, body) {
|
|
716
|
+
return this._http.request(
|
|
717
|
+
"POST",
|
|
718
|
+
`/v1/tenants/${encodeURIComponent(tenantId)}/webhooks`,
|
|
719
|
+
{ api_name: this.apiName, data: body }
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
async patch(tenantId, webhookId, body) {
|
|
723
|
+
return this._http.request(
|
|
724
|
+
"PATCH",
|
|
725
|
+
`/v1/tenants/${encodeURIComponent(tenantId)}/webhooks/${encodeURIComponent(webhookId)}`,
|
|
726
|
+
{ api_name: this.apiName, data: body }
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
async delete(tenantId, webhookId) {
|
|
730
|
+
const instance = this._http.getClient(this.apiName);
|
|
731
|
+
await instance.request({
|
|
732
|
+
method: "DELETE",
|
|
733
|
+
url: `/v1/tenants/${encodeURIComponent(tenantId)}/webhooks/${encodeURIComponent(webhookId)}`
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
async test(tenantId, webhookId) {
|
|
737
|
+
return this._http.request(
|
|
738
|
+
"POST",
|
|
739
|
+
`/v1/tenants/${encodeURIComponent(tenantId)}/webhooks/${encodeURIComponent(webhookId)}/test`,
|
|
740
|
+
{ api_name: this.apiName }
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
|
|
683
745
|
// src/client/client.ts
|
|
684
746
|
var Client = class extends HttpClient {
|
|
685
747
|
_tenant;
|
|
@@ -687,6 +749,8 @@ var Client = class extends HttpClient {
|
|
|
687
749
|
_advisory;
|
|
688
750
|
_explain;
|
|
689
751
|
_events;
|
|
752
|
+
_webhooks;
|
|
753
|
+
_quotas;
|
|
690
754
|
/**
|
|
691
755
|
* @param config - Resolved SDK configuration (typically `session.config`).
|
|
692
756
|
* @param axiosInstances - Optional per-API `axios` instances; see {@link Session#createClient}.
|
|
@@ -699,6 +763,8 @@ var Client = class extends HttpClient {
|
|
|
699
763
|
this._advisory = new AdvisoryResource(this);
|
|
700
764
|
this._explain = new ExplainResource(this);
|
|
701
765
|
this._events = new EventResource(this);
|
|
766
|
+
this._webhooks = new WebhookResource(this);
|
|
767
|
+
this._quotas = new QuotaResource(this);
|
|
702
768
|
}
|
|
703
769
|
/**
|
|
704
770
|
* Tenant API surface (credentials verification).
|
|
@@ -740,6 +806,24 @@ var Client = class extends HttpClient {
|
|
|
740
806
|
get events() {
|
|
741
807
|
return this._events;
|
|
742
808
|
}
|
|
809
|
+
/**
|
|
810
|
+
* Webhook endpoint management for a tenant (create, list, patch, delete, test delivery).
|
|
811
|
+
*
|
|
812
|
+
* All methods require `tenantId` explicitly; resolve it once via `client.tenant.verifyCredentials()`.
|
|
813
|
+
*
|
|
814
|
+
* @returns {@link WebhookResource} for the Query API.
|
|
815
|
+
*/
|
|
816
|
+
get webhooks() {
|
|
817
|
+
return this._webhooks;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Tenant quota configuration (get and upsert numeric caps).
|
|
821
|
+
*
|
|
822
|
+
* @returns {@link QuotaResource} for the Query API.
|
|
823
|
+
*/
|
|
824
|
+
get quotas() {
|
|
825
|
+
return this._quotas;
|
|
826
|
+
}
|
|
743
827
|
/**
|
|
744
828
|
* Flushes the event ingestion buffer (best-effort), then shuts down the batch scheduler.
|
|
745
829
|
* Call this on process shutdown so queued events are not lost.
|