@cookiemunch/sdk 0.7.1 → 0.9.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.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Operator token exchange (RFC 8693, CMU-X33): swap your identity provider's token for a short-lived,
3
+ * scoped Cookie Munch API key, so no long-lived key has to live in a file.
4
+ *
5
+ * import { exchangeOperatorToken, createCookieMunch } from '@cookiemunch/sdk';
6
+ * const { accessToken } = await exchangeOperatorToken({
7
+ * subjectToken: process.env.STAFF_ACCESS_TOKEN!, // from the sign-in your organisation trusts
8
+ * scope: ['sites:read', 'dsar:read'],
9
+ * resource: 'org:2f1c…', // or 'site:<cbid>' to lock the key to one site
10
+ * });
11
+ * const client = createCookieMunch({ apiKey: accessToken, baseUrl: 'https://api.cookiemunch.net' });
12
+ *
13
+ * No API key is needed for the exchange itself. The key it returns expires on its own (at most an hour).
14
+ */
15
+ export interface OperatorTokenExchangeInput {
16
+ /** The access or ID token from an OIDC issuer the deployment trusts. */
17
+ subjectToken: string;
18
+ /** Which kind of token `subjectToken` is. Default `access_token`. */
19
+ subjectTokenType?: 'access_token' | 'jwt' | 'id_token';
20
+ /** The API scopes the key should carry. Required: an exchanged key is never full access. */
21
+ scope: string[];
22
+ /** `org:<id>`, or `site:<cbid>` to lock the key to one site. */
23
+ resource: `org:${string}` | `site:${string}`;
24
+ /** Seconds, 60 to 3600. Default 3600. */
25
+ expiresIn?: number;
26
+ /** Default `https://api.cookiemunch.net`. */
27
+ baseUrl?: string;
28
+ fetch?: typeof fetch;
29
+ }
30
+ export interface OperatorTokenExchangeResult {
31
+ /** A scoped Cookie Munch API key. */
32
+ accessToken: string;
33
+ expiresIn: number;
34
+ scope: string[];
35
+ }
36
+ /** Thrown with the RFC 6749 `error` and `error_description` the server answered. */
37
+ export declare class TokenExchangeError extends Error {
38
+ readonly error: string;
39
+ readonly status: number;
40
+ constructor(error: string, status: number, description?: string);
41
+ }
42
+ export declare function exchangeOperatorToken(input: OperatorTokenExchangeInput): Promise<OperatorTokenExchangeResult>;
@@ -0,0 +1,637 @@
1
+ /**
2
+ * Request/response shapes for the Cookie Munch Developer API (/v1). These mirror the
3
+ * server's dev-api surface; they are defined here (rather than imported) so the SDK
4
+ * has no runtime/workspace dependency on the server package.
5
+ */
6
+ export interface Identity {
7
+ orgId: string;
8
+ orgName?: string | null;
9
+ /** The EFFECTIVE plan — the one every feature gate and limit uses. */
10
+ plan: string;
11
+ /**
12
+ * Why: `org` = this org's own subscription (or free, or a reseller-managed `child` plan);
13
+ * `account` = another org with the same owner pays for a higher plan that covers this one.
14
+ */
15
+ planSource: PlanSource;
16
+ /** What this org subscribes to itself (`free` = no subscription of its own). */
17
+ subscribedPlan: string;
18
+ /** Features the effective plan unlocks (e.g. `whiteLabel`, `tcf`, `sso`, `abTesting`). */
19
+ features: string[];
20
+ keyPrefix: string;
21
+ /** null = full access. */
22
+ scopes?: string[] | null;
23
+ unscoped?: boolean;
24
+ propertyLocked?: boolean;
25
+ cbids?: string[];
26
+ reseller?: boolean;
27
+ /** This organisation is a sandbox (`sandbox.create()`): test keys, test rights requests, never billed. */
28
+ sandbox?: boolean;
29
+ /** For a sandbox: the live organisation it belongs to. */
30
+ sandboxOf?: string;
31
+ }
32
+ export type PlanSource = 'org' | 'account';
33
+ /** One organisation on an account (GET /v1/orgs, and the `org_limit` refusal). */
34
+ export interface AccountOrg {
35
+ id: string;
36
+ name: string;
37
+ /** The effective plan. */
38
+ plan: string;
39
+ planSource: PlanSource;
40
+ subscribedPlan: string;
41
+ /** Present on a reseller-managed child organisation. */
42
+ parentOrgId?: string;
43
+ createdAt: number;
44
+ /** True for the organisation the calling key belongs to. */
45
+ current: boolean;
46
+ }
47
+ export interface AccountOrgs {
48
+ orgs: AccountOrg[];
49
+ /** The per-account org cap: `max` (null = unlimited), the `plan` that sets it, and `used`. */
50
+ orgLimit: {
51
+ max: number | null;
52
+ plan: string;
53
+ used: number;
54
+ };
55
+ /** The dashboard billing page, where a plan is chosen. */
56
+ upgradeUrl: string;
57
+ }
58
+ export interface Site {
59
+ cbid: string;
60
+ orgId: string;
61
+ domain: string;
62
+ /** The business name data subjects see on the rights page, in notices and the policy. Absent: the domain is shown. */
63
+ name?: string;
64
+ /** Other hosts serving the site: accepted consent-ingest origins, never verified claims. */
65
+ aliases?: string[];
66
+ verified?: boolean;
67
+ }
68
+ /** PATCH /v1/sites/{cbid} — rename or move a site. `name: null` clears the name; `aliases: []` clears them. */
69
+ export interface SiteUpdate {
70
+ domain?: string;
71
+ name?: string | null;
72
+ aliases?: string[];
73
+ }
74
+ export interface SiteUpdateResult {
75
+ dryRun?: true;
76
+ site: Site;
77
+ diff: Array<{
78
+ field: 'domain' | 'name' | 'aliases' | 'verified';
79
+ before: unknown;
80
+ after: unknown;
81
+ }>;
82
+ effects: {
83
+ /** `reset`: the domain moved; verify the new one. */
84
+ verification: 'kept' | 'reset';
85
+ scanTarget: string;
86
+ previewDefaultUrl: string;
87
+ displayName: string;
88
+ /** Custom rights-page hostnames no longer under the domain or an alias (left in place). */
89
+ staleRightsDomains: string[];
90
+ };
91
+ warnings?: Array<{
92
+ field: string;
93
+ reason: string;
94
+ }>;
95
+ }
96
+ /** One row of GET /v1/integration-health. `ok: null` = the check could not run (see `detail`). */
97
+ export interface HealthRow {
98
+ /** The suite shape's name for `check` (always equal). */
99
+ id: string;
100
+ check: string;
101
+ /** The generic check this row is an instance of (`forumulate.webhook` → `webhook.delivery`); otherwise `check`. */
102
+ kind: string;
103
+ /** The vendor a vendor-specific instance is about. */
104
+ vendor?: string;
105
+ ok: boolean | null;
106
+ severity: 'error' | 'warning' | 'info';
107
+ subject: string;
108
+ fixHint?: string;
109
+ /** The same as fixHint (suite shape). */
110
+ fix?: string;
111
+ /** Where the check and its fix are explained. */
112
+ docs: string;
113
+ detail?: string;
114
+ /** Network checks: the hop that failed (or, on success, the last one reached). */
115
+ hop?: 'dns' | 'tls' | 'http' | 'challenge' | 'auth';
116
+ status?: number;
117
+ elapsedMs?: number;
118
+ /** Failed network checks: a stable code (ENOTFOUND, timeout, challenge, http_503, …). */
119
+ error?: string;
120
+ /** A waiver covers this failing row; `ok` is then null. */
121
+ waived?: {
122
+ reason: string;
123
+ by: string;
124
+ until: number | null;
125
+ };
126
+ lastCheckedAt: number;
127
+ }
128
+ export interface IntegrationHealthReport {
129
+ cbid: string;
130
+ checkedAt: number;
131
+ generatedAt: string;
132
+ ok: boolean;
133
+ counts: {
134
+ error: number;
135
+ warning: number;
136
+ notChecked: number;
137
+ waived: number;
138
+ };
139
+ checks: HealthRow[];
140
+ }
141
+ /** PUT /v1/integration-health/waivers. */
142
+ export interface HealthWaiverInput {
143
+ /** Optional when the organisation has one site. */
144
+ cbid?: string;
145
+ /** A row's `check` or `kind`. */
146
+ check: string;
147
+ /** A row's `subject`; omitted: every subject of the check (`*`). */
148
+ subject?: string;
149
+ /** Why this is deliberate (1–500 characters). */
150
+ reason: string;
151
+ /** ISO date or epoch ms, future, within a year; omitted or null: never expires. */
152
+ until?: string | number | null;
153
+ }
154
+ export interface HealthWaiver {
155
+ cbid: string;
156
+ check: string;
157
+ subject: string;
158
+ reason: string;
159
+ by: string;
160
+ until: number | null;
161
+ createdAt: number;
162
+ expired?: boolean;
163
+ }
164
+ /** POST /v1/provision. Secrets are used for the request only and never returned. */
165
+ export interface ProvisionRequest {
166
+ product: string;
167
+ domain: string;
168
+ name?: string;
169
+ aliases?: string[];
170
+ cbid?: string;
171
+ /** `frontendApiHost` (e.g. auth.example.com) lets provision declare Atlas's sign-in cookies on the site. */
172
+ /** `rightsIdentity` (default true) makes Atlas the site's rights sign-in (step `rights.identity`). */
173
+ atlas?: {
174
+ secretKey: string;
175
+ cookieConsent?: boolean;
176
+ dsrWebhook?: boolean;
177
+ rightsIdentity?: boolean;
178
+ frontendApiHost?: string;
179
+ };
180
+ forumulate?: {
181
+ token: string;
182
+ orgId: string;
183
+ consent?: boolean;
184
+ executorKey?: boolean;
185
+ label?: string;
186
+ };
187
+ rightsApi?: {
188
+ baseUrl: string;
189
+ secretKey: string;
190
+ profilePreset?: string;
191
+ };
192
+ }
193
+ export interface ProvisionStep {
194
+ /** v2 answers: the stable step id (`fromStep` takes it). */
195
+ id?: string;
196
+ step: string;
197
+ status: 'created' | 'updated' | 'unchanged' | 'would_create' | 'would_update' | 'skipped' | 'waiting' | 'waiting_on' | 'failed' | 'passed';
198
+ detail?: string;
199
+ diff?: Array<{
200
+ field: string;
201
+ before: unknown;
202
+ after: unknown;
203
+ }>;
204
+ /** A string in v1 answers; `{ platform, items }` in v2 answers. */
205
+ waitingOn?: string | {
206
+ platform: string;
207
+ items: string[];
208
+ };
209
+ }
210
+ export interface ProvisionResult {
211
+ dryRun: boolean;
212
+ product: string;
213
+ cbid: string | null;
214
+ steps: ProvisionStep[];
215
+ summary: {
216
+ changed: number;
217
+ unchanged: number;
218
+ waiting: number;
219
+ failed: number;
220
+ };
221
+ /** v2 answers only. */
222
+ version?: 2;
223
+ ids?: {
224
+ orgId: string;
225
+ cbid: string | null;
226
+ executors: Record<string, string>;
227
+ webhooks: string[];
228
+ };
229
+ sealedSecrets?: Array<Record<string, unknown>>;
230
+ checklist?: string[];
231
+ }
232
+ /** A partner the deployment knows: built in (forumulate, atlas, chatly, referalli) or any other registered on it. */
233
+ export type PartnerId = 'forumulate' | 'atlas' | 'chatly' | 'referalli' | (string & {});
234
+ export interface PartnerLinkCreate {
235
+ partner: PartnerId;
236
+ /** The partner's id for the receiving account (a Forumulate organisation id, an Atlas instance id). */
237
+ externalId: string;
238
+ label?: string;
239
+ events?: Array<'consent.recorded' | 'consent.withdrawn'>;
240
+ }
241
+ export interface PartnerLink {
242
+ id: string;
243
+ cbid: string;
244
+ partner: PartnerId;
245
+ externalId: string;
246
+ label?: string;
247
+ events: string[];
248
+ status: 'active' | 'waiting_on_partner' | 'revoked';
249
+ /** e.g. "Forumulate (docs) — receiving consent events". */
250
+ summary: string;
251
+ waitingOn?: string;
252
+ createdAt: number;
253
+ createdBy?: string;
254
+ revokedAt?: number;
255
+ revokedBy?: string | null;
256
+ lastDeliveryAt: number | null;
257
+ lastError: string | null;
258
+ }
259
+ export interface PartnerCatalogEntry {
260
+ id: PartnerId;
261
+ label: string;
262
+ externalIdLabel: string;
263
+ events: string[];
264
+ available: boolean;
265
+ waitingOn?: string;
266
+ }
267
+ export interface SiteCreate {
268
+ domain: string;
269
+ /** Optional; the server auto-generates a cbid when omitted. */
270
+ cbid?: string;
271
+ }
272
+ /** Site config is an open, deeply-nested object owned by @cookiemunch/core. */
273
+ export type SiteConfig = Record<string, unknown>;
274
+ export interface ConsentDay {
275
+ Date: string;
276
+ OptIn: number;
277
+ OptOut: number;
278
+ OptInImplied: number;
279
+ OptInStrict: number;
280
+ TypeOptInPref: number;
281
+ TypeOptInStat: number;
282
+ TypeOptInMark: number;
283
+ Impressions: number;
284
+ Countries: Record<string, number>;
285
+ }
286
+ export interface ConsentLogRow {
287
+ stamp: string;
288
+ receivedAt: number;
289
+ region: string;
290
+ method: string;
291
+ choices: {
292
+ preferences: boolean;
293
+ statistics: boolean;
294
+ marketing: boolean;
295
+ };
296
+ anonIp: string;
297
+ url: string;
298
+ }
299
+ export interface RangeQuery {
300
+ from?: number;
301
+ to?: number;
302
+ }
303
+ export interface LogQuery extends RangeQuery {
304
+ limit?: number;
305
+ }
306
+ export type DsarType = 'access' | 'deletion' | 'rectification' | 'portability' | 'opt-out' | 'closure' | 'restriction';
307
+ /** A type word as sent: the canonical words, or an alias (`erasure`/`erase` → deletion, `export` → access). */
308
+ export type DsarTypeWord = DsarType | 'erasure' | 'erase' | 'export';
309
+ export type Regulation = 'gdpr' | 'ccpa';
310
+ export type DsarStatus = 'received' | 'verifying' | 'in_progress' | 'completed' | 'rejected' | 'cancelled';
311
+ export interface DsarRequest {
312
+ id: string;
313
+ type: DsarType;
314
+ subjectEmail: string;
315
+ regulation: Regulation;
316
+ status: DsarStatus;
317
+ createdAt: number;
318
+ dueAt: number;
319
+ /** Where the subject said they are, when the intake captured it. */
320
+ jurisdiction?: string;
321
+ /** INTERNAL: intake note plus the audit lines `/erase` and `/export` append. Never shown to the subject. */
322
+ note?: string;
323
+ /** The line the subject reads in the response notice. */
324
+ messageToSubject?: string;
325
+ /** A test request: off the SLA board, labelled, and deletable with `dsar.delete`. A closed request can be marked one with `dsar.markTest`. */
326
+ test?: boolean;
327
+ /** Its type in the suite vocabulary (`erasure` was stored as `deletion`). */
328
+ canonicalType?: string;
329
+ /** The case's X-Trace-Id, when it has one. */
330
+ trace?: string;
331
+ }
332
+ /** A signed-in identity from any OIDC issuer the site trusts. `(iss, sub)` is the key. */
333
+ export interface SubjectIdentity {
334
+ iss: string;
335
+ sub: string;
336
+ ext?: string;
337
+ principal?: string;
338
+ }
339
+ /** Who a case is about. `atlas` is accepted as an alias of `identity`. */
340
+ export interface DsarSubject {
341
+ emails: string[];
342
+ identity?: SubjectIdentity;
343
+ atlas?: SubjectIdentity;
344
+ }
345
+ /** POST /v1/dsar/requests. */
346
+ export interface DsarCaseCreate {
347
+ cbid: string;
348
+ type: DsarTypeWord;
349
+ subject?: DsarSubject;
350
+ /** The person's own token from the site's sign-in issuer (aud `cookiemunch:<cbid>`). */
351
+ subjectToken?: string;
352
+ /** product and atlas need dsar:write and attest the person; admin (default) files it received. */
353
+ /** `identity`: the site's sign-in provider. `atlas` is its former name. */
354
+ source?: 'product' | 'identity' | 'admin' | 'atlas';
355
+ reason?: string;
356
+ /** Connections not to ask: system names, connection ids, or `self`. */
357
+ skipSystems?: string[];
358
+ /** Default true. */
359
+ fulfill?: boolean;
360
+ regulation?: 'gdpr' | 'ccpa' | 'lgpd' | 'pipeda' | 'vcdpa';
361
+ jurisdiction?: string;
362
+ test?: boolean;
363
+ }
364
+ export type DsarCaseStatus = 'pending' | 'scheduled' | 'processing' | 'completed' | 'completed_with_retention' | 'rejected' | 'cancelled';
365
+ export interface RetentionLine {
366
+ category: string;
367
+ basis: 'legal_obligation' | 'legal_claims' | 'contract' | 'public_interest' | 'legitimate_interest';
368
+ until?: string;
369
+ }
370
+ export interface DsarCaseTask {
371
+ system: string;
372
+ executorId: string | null;
373
+ taskId: string;
374
+ status: DsarCaseStatus | 'no_match';
375
+ requestId: string | null;
376
+ eta: string | null;
377
+ completedAt: string | null;
378
+ detail: string | null;
379
+ retention?: RetentionLine[];
380
+ }
381
+ /** A case across every connected system. */
382
+ export interface DsarCase {
383
+ id: string;
384
+ type: DsarType;
385
+ canonicalType: string;
386
+ status: DsarCaseStatus;
387
+ requestStatus: DsarStatus;
388
+ cbid?: string;
389
+ source: 'product' | 'identity' | 'rights_page' | 'admin' | 'atlas' | null;
390
+ reason: string | null;
391
+ openedAt: string;
392
+ /** Nothing is erased anywhere before this. */
393
+ executeAfter: string | null;
394
+ graceDays: number | null;
395
+ closedAt: string | null;
396
+ /** False for a closure: no statutory clock. */
397
+ statutory: boolean;
398
+ dueAt: string | null;
399
+ verification: string | null;
400
+ trace: string | null;
401
+ subject: {
402
+ emails: string[];
403
+ identity: SubjectIdentity | null;
404
+ };
405
+ skipSystems: string[];
406
+ fulfill: boolean | null;
407
+ cancelRequestedAt: string | null;
408
+ test: boolean;
409
+ tasks: DsarCaseTask[];
410
+ receiptUrl: string;
411
+ /** Lets the person who asked read this case through POST /api/v1/public/dsar/status. */
412
+ statusToken?: string;
413
+ }
414
+ /** One signed receipt for a case (Ed25519; verify with GET /v1/partner/jwks.json). */
415
+ export interface DsarCaseReceipt {
416
+ receiptVersion: number;
417
+ caseId: string;
418
+ type: DsarType;
419
+ canonicalType: string;
420
+ status: DsarCaseStatus;
421
+ openedAt: string;
422
+ executeAfter: string | null;
423
+ closedAt: string | null;
424
+ verification: string | null;
425
+ trace: string | null;
426
+ subject: {
427
+ emailHashes: string[];
428
+ identityHash?: string;
429
+ atlasSubHash?: string;
430
+ };
431
+ systems: Array<{
432
+ system: string;
433
+ executorId: string | null;
434
+ requestId: string | null;
435
+ status: string;
436
+ completedAt: string | null;
437
+ trace: string | null;
438
+ retention?: RetentionLine[];
439
+ }>;
440
+ issuedAt: string;
441
+ signature: {
442
+ alg: 'Ed25519';
443
+ kid: string;
444
+ value: string;
445
+ covers?: string;
446
+ };
447
+ }
448
+ /** A site's rights-request settings. */
449
+ export interface DsarSettings {
450
+ cbid: string;
451
+ /** Days an erasure or account closure waits before any system acts (0–30). 7 for a new site; 0 for one that existed before (opt-in). */
452
+ graceDays: number;
453
+ /** site: set explicitly; default: a new site's 7 days; legacy: an existing site, 0 until set. */
454
+ source: 'site' | 'default' | 'legacy';
455
+ updatedAt: number | null;
456
+ }
457
+ /** GET /v1/integrations/{app}/card. */
458
+ export interface SubjectCard {
459
+ app: string;
460
+ openCases: Array<{
461
+ id: string;
462
+ type: string;
463
+ status: string;
464
+ executeAfter: string | null;
465
+ openedAt: string;
466
+ receiptUrl: string;
467
+ }>;
468
+ consent: null;
469
+ }
470
+ /** POST /v1/integrations/{provider}/connect. */
471
+ export interface IntegrationConnect {
472
+ /** The site the provider opens cases on. */
473
+ cbid: string;
474
+ /** The provider instance's secret key, used for this one call; never stored or echoed. */
475
+ providerKey: string;
476
+ /** Only for a provider with no built-in connector. A body value "{apiKey}" becomes the minted key. */
477
+ connector?: {
478
+ url: string;
479
+ method?: 'PUT' | 'POST';
480
+ body: Record<string, unknown>;
481
+ };
482
+ }
483
+ export interface IntegrationConnectResult {
484
+ connected?: boolean;
485
+ dryRun?: true;
486
+ provider: string;
487
+ cbid: string;
488
+ /** The minted key's metadata; the key itself is never returned. */
489
+ key: {
490
+ prefix?: string;
491
+ name: string;
492
+ owner: string;
493
+ scopes: string[];
494
+ cbids: string[];
495
+ expiresAt?: number | null;
496
+ expiresInDays?: number;
497
+ };
498
+ request: {
499
+ method: 'PUT' | 'POST';
500
+ url: string;
501
+ status?: number;
502
+ };
503
+ /** Only on a dry run: the body that would be sent, the key shown as a placeholder. */
504
+ requestBody?: Record<string, unknown>;
505
+ /** Earlier keys for this provider and site, revoked now. */
506
+ revoked?: string[];
507
+ wouldRevoke?: string[];
508
+ }
509
+ /**
510
+ * How a rights-page requester was verified — on every answer of the public intake
511
+ * (`POST /api/v1/public/dsar`, `/verify`, `/resend`) and so in the `dsar.created` webhook payload.
512
+ * `email_code` on a 202: a code was mailed through your own provider; on a 201: the code matched.
513
+ * `none` with a `reason`: filed without verification — confirm the requester yourself. `none`
514
+ * without one: an opt-out, which needs no verification.
515
+ */
516
+ export type PublicVerification = {
517
+ method: 'session' | 'email_code' | 'account_link';
518
+ } | {
519
+ method: 'none';
520
+ reason?: 'no_verification_configured' | 'mail_failed' | 'recipient_rejected';
521
+ };
522
+ /** A public intake that filed a request (201): also the `dsar.created` payload for one. */
523
+ export interface PublicDsarFiled {
524
+ id: string;
525
+ status: 'received' | 'verifying';
526
+ /** Epoch ms the statutory response is due. */
527
+ deadline: number;
528
+ /** Only when filed without verification; the same as `verification.method` `none` with a `reason`. */
529
+ unverified?: true;
530
+ verification: PublicVerification;
531
+ }
532
+ /** A public intake waiting for its email code (202). */
533
+ export interface PublicDsarPending {
534
+ pendingId: string;
535
+ expiresAt: number;
536
+ resendAfter?: number;
537
+ verification: {
538
+ method: 'email_code';
539
+ };
540
+ }
541
+ export interface DsarCreate {
542
+ type: DsarType;
543
+ subjectEmail: string;
544
+ /** Picks the statutory response window. */
545
+ regulation: Regulation;
546
+ /**
547
+ * Where the subject says they are (ISO 3166 country or country-subdivision, e.g.
548
+ * `US-TX`). The response notice names the statute that governs there — without it a
549
+ * Texan is told their request was handled under a Virginia law, since every US state
550
+ * shares one `regulation` for the deadline.
551
+ */
552
+ jurisdiction?: string;
553
+ /** INTERNAL: kept on the request, never shown to the subject. */
554
+ note?: string;
555
+ /** The one free-text line the subject reads in the notice (≤2000 chars; an API key or key prefix is refused). */
556
+ messageToSubject?: string;
557
+ /** The property this request belongs to; decides which connected systems answer it. */
558
+ cbid?: string;
559
+ /** The subject's consent-receipt id. */
560
+ stamp?: string;
561
+ /**
562
+ * Mark it as a test: left out of SLA metrics and the deadline board, labelled as a test,
563
+ * and deletable with `dsar.delete(id)`. For end-to-end checks of a rights flow.
564
+ */
565
+ test?: boolean;
566
+ }
567
+ export interface VendorInput {
568
+ name: string;
569
+ category: string;
570
+ dataShared: string[];
571
+ dpaSigned: boolean;
572
+ subprocessors: number;
573
+ certifications: string[];
574
+ region: string;
575
+ }
576
+ export interface RiskScore {
577
+ score: number;
578
+ band: 'low' | 'medium' | 'high';
579
+ }
580
+ /** A stored vendor record (no computed risk; see {@link ScoredVendor}). Returned as the
581
+ * `vendor` field of POST /v1/vendors. */
582
+ export interface VendorRecord extends VendorInput {
583
+ id: string;
584
+ }
585
+ /** A vendor as listed, with its computed risk flattened in. */
586
+ export interface ScoredVendor extends VendorRecord {
587
+ risk: RiskScore;
588
+ }
589
+ export type LegalBasis = 'consent' | 'contract' | 'legal-obligation' | 'vital-interests' | 'public-task' | 'legitimate-interests';
590
+ export interface RopaInput {
591
+ name: string;
592
+ purpose: string;
593
+ legalBasis: LegalBasis;
594
+ dataCategories: string[];
595
+ recipients: string[];
596
+ retentionDays: number;
597
+ crossBorderTransfer: boolean;
598
+ }
599
+ export interface RopaEntry extends RopaInput {
600
+ id: string;
601
+ }
602
+ /** A signed consent receipt (shape owned by @cookiemunch/receipts). */
603
+ export type SignedReceipt = Record<string, unknown>;
604
+ /** How a preference-center opt-in was captured. Mirrors @cookiemunch/preferences OptInMethod. */
605
+ export type OptInMethod = 'single-opt-in' | 'double-opt-in';
606
+ /**
607
+ * A subject's saved preference-center record (identity-keyed named purposes, distinct
608
+ * from the org-level {@link PreferenceItem} catalog). Matches @cookiemunch/preferences
609
+ * PreferenceRecord (mirrored here per this package's no-server-dependency convention).
610
+ * Returned wrapped as `{ record }` by POST /v1/preferences.
611
+ */
612
+ export interface PreferenceRecord {
613
+ subjectId: string;
614
+ purposes: Record<string, boolean>;
615
+ method: OptInMethod;
616
+ confirmed: boolean;
617
+ updatedAt: number;
618
+ version: number;
619
+ }
620
+ /** GET /v1/categories/registry — the reserved category templates (CMU-X15). */
621
+ export interface CategoryRegistry {
622
+ version: 1;
623
+ standard: string[];
624
+ suite: Array<{
625
+ id: 'support_chat' | 'referrals';
626
+ stance: 'necessary_on_request' | 'opt_in_eu_uk';
627
+ fallback: 'preferences' | 'marketing';
628
+ /** By banner language. */
629
+ labels: Record<string, string>;
630
+ descriptions: Record<string, string>;
631
+ basis?: string[];
632
+ vendors?: Array<{
633
+ product: string;
634
+ storage: string[];
635
+ }>;
636
+ }>;
637
+ }
@@ -0,0 +1,22 @@
1
+ export interface VerifyWebhookInput {
2
+ /** The raw request body, exactly as received. Do NOT re-stringify parsed JSON. */
3
+ payload: string;
4
+ /** The `X-CookieMunch-Signature` header value. */
5
+ signature: string | undefined | null;
6
+ /** The `X-CookieMunch-Timestamp` header value (unix seconds). */
7
+ timestamp: string | number | undefined | null;
8
+ /** The subscription secret (`whsec_…`), shown once when the webhook was created. */
9
+ secret: string;
10
+ /**
11
+ * Maximum age (in seconds) of a delivery before it is rejected as stale/replayed.
12
+ * Defaults to 300 (5 minutes). Pass 0 to disable the freshness check entirely.
13
+ */
14
+ toleranceSeconds?: number;
15
+ /** Current time in ms (injectable for tests); defaults to `Date.now()`. */
16
+ now?: number;
17
+ }
18
+ /**
19
+ * Returns true only when the signature is valid AND (unless disabled) the timestamp is fresh.
20
+ * Fail-closed: any missing/malformed input returns false rather than throwing.
21
+ */
22
+ export declare function verifyWebhookSignature(input: VerifyWebhookInput): boolean;