spaps-sdk 1.10.1 → 1.11.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/CHANGELOG.md CHANGED
@@ -8,6 +8,18 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
8
8
 
9
9
  ### Added
10
10
 
11
+ - Added browser-safe `entitlements.listCurrentUserProjects()` and `entitlements.checkCurrentUserProjectAccess(...)` helpers for project grant reads.
12
+
13
+ ## [1.10.2] - 2026-06-04
14
+
15
+ ### Added
16
+
17
+ - Added `issueReporting.getAttachmentAccess(attachmentId)` as the canonical screenshot access helper while keeping `getAttachmentAccessUrl` as a backward-compatible alias.
18
+
19
+ ## [1.10.1] - 2026-05-16
20
+
21
+ ### Added
22
+
11
23
  - Added issue-reporting screenshot attachment helpers for private upload, list, access URL, delete, and attach-by-ID create/update/reply flows.
12
24
 
13
25
  ## [1.10.0] - 2026-05-11
package/PERMISSIONS.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  Client-side permission checking and role management utilities for SPAPS applications.
4
4
 
5
+ ## Browser-Safe Project Access
6
+
7
+ The SDK exposes read-only helpers for current-user project access:
8
+
9
+ ```typescript
10
+ await spaps.entitlements.listCurrentUserProjects({
11
+ entitlementKey: 'pds.project.viewer'
12
+ });
13
+
14
+ await spaps.entitlements.checkCurrentUserProjectAccess({
15
+ projectId: 'project_123',
16
+ entitlementKey: 'pds.project.viewer'
17
+ });
18
+ ```
19
+
20
+ These helpers are safe for publishable-key browser contexts because SPAPS
21
+ validates the JWT subject server-side. The SDK does not expose browser methods
22
+ for account membership invitation, project grant, project revoke, or account
23
+ capability mutation. Keep those operations in a trusted backend using a secret
24
+ key and admin access token.
25
+
5
26
  ## 🚀 Quick Start
6
27
 
7
28
  ```typescript
@@ -387,4 +408,4 @@ interface PermissionCheckResult {
387
408
  4. **Role-based UI**: Show/hide features based on user permissions
388
409
  5. **Custom admin management**: Use custom admin lists for multi-tenant apps
389
410
  6. **Testing**: Test permission logic with various user states
390
- 7. **Type safety**: Use TypeScript interfaces for better development experience
411
+ 7. **Type safety**: Use TypeScript interfaces for better development experience
package/README.md CHANGED
@@ -23,7 +23,7 @@ This package targets `Node.js >=14`.
23
23
 
24
24
  | Need | Package gives you |
25
25
  | --- | --- |
26
- | One client for many SPAPS surfaces | `auth`, `payments`, `sessions`, `secureMessages`, `issueReporting`, `appLinks`, `email`, `entitlements`, `usage`, `skillEvals`, `dayrate`, `admin`, and `cfo` namespaces |
26
+ | One client for many SPAPS surfaces | `auth`, `payments`, `sessions`, `secureMessages`, `issueReporting`, `appLinks`, `marketing`, `email`, `entitlements`, `usage`, `skillEvals`, `dayrate`, `admin`, and `cfo` namespaces |
27
27
  | Local development without extra config | Localhost URLs automatically enable local mode |
28
28
  | Browser and server usage | `publishableKey`, `secretKey`, or legacy `apiKey` support |
29
29
  | Shared contracts | Re-exports a large slice of `spaps-types` |
@@ -86,8 +86,9 @@ Relevant environment variables:
86
86
  | `secureMessages` | Secure-message create/list helpers |
87
87
  | `issueReporting` | Status, history, create, update, reply, voice-token, and private screenshot attachment flows |
88
88
  | `appLinks` | Authenticated create and public resolve helpers for application-scoped short links |
89
+ | `marketing` | Browser-safe attribution/experiment event emission and server-side experiment results |
89
90
  | `email` | Template lookup, preview, and send helpers |
90
- | `entitlements` | User and resource entitlement queries |
91
+ | `entitlements` | User/resource entitlement queries and browser-safe current-user project access reads |
91
92
  | `usage` | Secret-key usage authorization and immutable usage recording |
92
93
  | `skillEvals` | Paid blind skill-eval cases, review rooms, reviewer marks, insight inboxes, and controlled reveal |
93
94
  | `dayrate` | Availability, Stripe booking, x402 booking-hold, and checkout-status helpers |
@@ -96,6 +97,33 @@ Relevant environment variables:
96
97
 
97
98
  ## Common Patterns
98
99
 
100
+ ### Browser-Safe Project Access Reads
101
+
102
+ Use a publishable key and an authenticated user JWT in browser code. These
103
+ helpers only read project access for the current user; membership invitation,
104
+ project grant, project revoke, and account capability mutation remain
105
+ server-only operations.
106
+
107
+ ```ts
108
+ const spaps = new SPAPSClient({
109
+ apiUrl: "https://api.example.test",
110
+ publishableKey: "spaps_pub_example",
111
+ });
112
+
113
+ spaps.setAccessToken(userAccessToken);
114
+
115
+ const projects = await spaps.entitlements.listCurrentUserProjects({
116
+ entitlementKey: "pds.project.viewer",
117
+ });
118
+
119
+ const access = await spaps.entitlements.checkCurrentUserProjectAccess({
120
+ projectId: "project_123",
121
+ entitlementKey: "pds.project.viewer",
122
+ });
123
+
124
+ console.log(projects.count, access.has_access);
125
+ ```
126
+
99
127
  ### Typed Secure Messages
100
128
 
101
129
  ```ts
@@ -149,7 +177,7 @@ const issue = await spaps.issueReporting.create({
149
177
  attachment_ids: [attachment.id],
150
178
  });
151
179
 
152
- const access = await spaps.issueReporting.getAttachmentAccessUrl(attachment.id);
180
+ const access = await spaps.issueReporting.getAttachmentAccess(attachment.id);
153
181
  console.log(issue.id, access.expires_in_seconds);
154
182
  ```
155
183
 
@@ -174,6 +202,41 @@ const link = await spaps.appLinks.create({
174
202
  });
175
203
 
176
204
  console.log(`/${link.app_slug}/${link.username}/${link.slug}`);
205
+
206
+ await spaps.appLinks.update(link.username, link.slug, {
207
+ metadata: { diagram_state: "pako:new-state" },
208
+ });
209
+ ```
210
+
211
+ ### Marketing Events
212
+
213
+ Use `marketing.emit` in the browser with a publishable key for anonymous
214
+ attribution touches or experiment exposures. Use `marketing.getExperimentResults`
215
+ from a trusted server or agent with a secret key to read revenue-backed results
216
+ and the conservative stop signal.
217
+
218
+ ```ts
219
+ const browserSpaps = new SPAPSClient({
220
+ apiUrl: "https://api.example.test",
221
+ publishableKey: "spaps_pub_example",
222
+ });
223
+
224
+ await browserSpaps.marketing.emit({
225
+ anon_id: "anon_01HY...",
226
+ event_type: "experiment_exposure",
227
+ experiment_id: "landing-hero-copy",
228
+ variant_id: "treatment",
229
+ dedupe_key: "landing-hero-copy:anon_01HY:treatment",
230
+ timestamp: new Date().toISOString(),
231
+ });
232
+
233
+ const serverSpaps = new SPAPSClient({
234
+ apiUrl: "https://api.example.test",
235
+ secretKey: process.env.SPAPS_SECRET_KEY,
236
+ });
237
+
238
+ const results = await serverSpaps.marketing.getExperimentResults("landing-hero-copy");
239
+ console.log(results.decision.recommendation, results.decision.winner_variant_id);
177
240
  ```
178
241
 
179
242
  ### Server-Side Usage Control
@@ -448,7 +511,7 @@ npm run test:readme
448
511
  ## Metadata
449
512
 
450
513
  - `package_name`: `spaps-sdk`
451
- - `latest_version`: `1.10.0`
514
+ - `latest_version`: `1.10.2`
452
515
  - `minimum_runtime`: `Node.js >=14.0.0`
453
516
  - `api_base_url`: `https://api.sweetpotato.dev`
454
517
 
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as spaps_types from 'spaps-types';
2
- import { ResourceType, Entitlement, CreateProductRequest, Product, UpdateProductRequest, CreatePriceRequest, Price, ProductSyncResult, CryptoReconcileRequest, CreateSecureMessageRequest, SecureMessage, IssueReportScope, IssueReportStatusResult, IssueReportStatus, IssueReportListResult, IssueReport, CreateIssueReportRequest, IssueReportAttachmentOut, ListIssueReportAttachmentsResponse, IssueReportAttachmentAccessResponse, IssueReportingVoiceTokenResult, UpdateIssueReportRequest, ReplyIssueReportRequest, CreateAppLinkRequest, AppLink, AuthResponse, User as User$1, CreateCryptoInvoiceRequest, CryptoInvoiceStatusSnapshot, CheckoutSession, X402ResourceStatusResponse, X402ActionResponse, X402ReceiptResponse, X402ReceiptListResponse, X402HandoffVerification, DayrateAvailabilityResponse, DayrateBookingRequest, DayrateBookingResponse, DayrateMultiBookingRequest, DayrateMultiBookingResponse, DayrateX402BookingRequest, DayrateX402BookingResponse, DayrateCheckoutStatusResponse, UsageControlFeaturesResponse, UsageControlStatusRequest, UsageControlStatusResponse, UsageControlAuthorizeRequest, UsageControlAuthorizeResponse, UsageControlRecordRequest, UsageControlRecordResponse, UsageControlHistoryRequest, UsageControlHistoryResponse, Subscription, VerifyCryptoWebhookSignatureOptions } from 'spaps-types';
3
- export { AdminPermission, AdminRole, AdminUser, ApiResponse, AppLink, AuthResponse, CheckoutSession, CreateAppLinkRequest, CreateCryptoInvoiceRequest, CreateIssueReportRequest, CreatePriceRequest, CreateProductRequest, CreateSecureMessageInput, CreateSecureMessageRequest, CryptoInvoice, CryptoInvoiceResponse, CryptoInvoiceStatusSnapshot, CryptoReconcileRequest, DayrateAvailabilityResponse, DayrateAvailableSlot, DayrateBookingRequest, DayrateBookingResponse, DayrateCheckoutStatus, DayrateCheckoutStatusBooking, DayrateCheckoutStatusResponse, DayrateDayOfWeek, DayrateMultiBookingRequest, DayrateMultiBookingResponse, DayratePriceBreakdown, DayrateSlotType, DayrateX402BookingRequest, DayrateX402BookingResponse, Entitlement, IssueReport, IssueReportAttachmentAccessResponse, IssueReportAttachmentOut, IssueReportListResult, IssueReportStatus, IssueReportStatusResult, IssueReportTarget, IssueReportingInputMode, IssueReportingVoiceProvider, IssueReportingVoiceTokenResult, LinkedIssueReportCase, ListIssueReportAttachmentsResponse, Price, Product, ProductSyncResult, ReplyIssueReportRequest, ResourceType, SecureMessage, SecureMessageOutput, Subscription, TokenPair, UpdateIssueReportRequest, UpdateProductRequest, UsageControlAuthorizeRequest, UsageControlAuthorizeResponse, UsageControlDecision, UsageControlDimensions, UsageControlError, UsageControlErrorCode, UsageControlFeaturesResponse, UsageControlHistoryRequest, UsageControlHistoryResponse, UsageControlLedgerEvent, UsageControlRecordRequest, UsageControlRecordResponse, UsageControlRecordStatus, UsageControlStatusRequest, UsageControlStatusResponse, User, UserProfile, UserRole, UserWallet, VerifyCryptoWebhookSignatureOptions, X402ActionFreeResponse, X402ActionOutcome, X402ActionPendingResponse, X402ActionReplayedResponse, X402ActionResponse, X402ActionSettledResponse, X402ExecuteActionRequest, X402HandoffAuthorization, X402HandoffVerification, X402HandoffVerifyRequest, X402PaymentAccept, X402PaymentRequirement, X402ProjectionStatus, X402Receipt, X402ReceiptListResponse, X402ReceiptResponse, X402ReceiptStatus, X402Resource, X402ResourceStatus, X402ResourceStatusResponse, atomicToMoneyUnits, createSecureMessageRequestSchema, isX402PaymentRequired, isX402ResourceStatus, moneyUnitsToAtomic, roundHalfToPositiveInfinity, secureMessageMetadataSchema, secureMessageSchema, validatePositiveAmountAtomic } from 'spaps-types';
2
+ import { ResourceType, Entitlement, CreateProductRequest, Product, UpdateProductRequest, CreatePriceRequest, Price, ProductSyncResult, CryptoReconcileRequest, CreateSecureMessageRequest, SecureMessage, ListProjectGrantsResponse, ProjectAccessCheckResponse, IssueReportScope, IssueReportStatusResult, IssueReportStatus, IssueReportListResult, IssueReport, CreateIssueReportRequest, IssueReportAttachmentOut, ListIssueReportAttachmentsResponse, IssueReportAttachmentAccessResponse, IssueReportingVoiceTokenResult, UpdateIssueReportRequest, ReplyIssueReportRequest, ListIssueReportMessagesResponse, CreateReporterMessageRequest, IssueReportMessage, CreateOperatorMessageRequest, RetractOperatorMessageRequest, CreateAppLinkRequest, AppLink, UpdateAppLinkRequest, AuthResponse, User as User$1, CreateCryptoInvoiceRequest, CryptoInvoiceStatusSnapshot, CheckoutSession, X402ResourceStatusResponse, X402ActionResponse, X402ReceiptResponse, X402ReceiptListResponse, X402HandoffVerification, DayrateAvailabilityResponse, DayrateBookingRequest, DayrateBookingResponse, DayrateMultiBookingRequest, DayrateMultiBookingResponse, DayrateX402BookingRequest, DayrateX402BookingResponse, DayrateCheckoutStatusResponse, UsageControlFeaturesResponse, UsageControlStatusRequest, UsageControlStatusResponse, UsageControlAuthorizeRequest, UsageControlAuthorizeResponse, UsageControlRecordRequest, UsageControlRecordResponse, UsageControlHistoryRequest, UsageControlHistoryResponse, Subscription, VerifyCryptoWebhookSignatureOptions } from 'spaps-types';
3
+ export { AdminPermission, AdminRole, AdminUser, ApiResponse, AppLink, AuthResponse, CheckoutSession, CreateAppLinkRequest, CreateCryptoInvoiceRequest, CreateIssueReportRequest, CreateOperatorMessageRequest, CreatePriceRequest, CreateProductRequest, CreateReporterMessageRequest, CreateSecureMessageInput, CreateSecureMessageRequest, CryptoInvoice, CryptoInvoiceResponse, CryptoInvoiceStatusSnapshot, CryptoReconcileRequest, DayrateAvailabilityResponse, DayrateAvailableSlot, DayrateBookingRequest, DayrateBookingResponse, DayrateCheckoutStatus, DayrateCheckoutStatusBooking, DayrateCheckoutStatusResponse, DayrateDayOfWeek, DayrateMultiBookingRequest, DayrateMultiBookingResponse, DayratePriceBreakdown, DayrateSlotType, DayrateX402BookingRequest, DayrateX402BookingResponse, Entitlement, IssueReport, IssueReportAttachmentAccessResponse, IssueReportAttachmentOut, IssueReportListResult, IssueReportMessage, IssueReportStatus, IssueReportStatusResult, IssueReportTarget, IssueReportingInputMode, IssueReportingVoiceProvider, IssueReportingVoiceTokenResult, LinkedIssueReportCase, ListIssueReportAttachmentsResponse, ListIssueReportMessagesResponse, ListProjectGrantsResponse, Price, Product, ProductSyncResult, ProjectAccessCheckResponse, ProjectGrant, ProjectGrantStatus, ReplyIssueReportRequest, ResourceType, RetractOperatorMessageRequest, SecureMessage, SecureMessageOutput, Subscription, TokenPair, UpdateAppLinkRequest, UpdateIssueReportRequest, UpdateProductRequest, UsageControlAuthorizeRequest, UsageControlAuthorizeResponse, UsageControlDecision, UsageControlDimensions, UsageControlError, UsageControlErrorCode, UsageControlFeaturesResponse, UsageControlHistoryRequest, UsageControlHistoryResponse, UsageControlLedgerEvent, UsageControlRecordRequest, UsageControlRecordResponse, UsageControlRecordStatus, UsageControlStatusRequest, UsageControlStatusResponse, User, UserProfile, UserRole, UserWallet, VerifyCryptoWebhookSignatureOptions, X402ActionFreeResponse, X402ActionOutcome, X402ActionPendingResponse, X402ActionReplayedResponse, X402ActionResponse, X402ActionSettledResponse, X402ExecuteActionRequest, X402HandoffAuthorization, X402HandoffVerification, X402HandoffVerifyRequest, X402PaymentAccept, X402PaymentRequirement, X402ProjectionStatus, X402Receipt, X402ReceiptListResponse, X402ReceiptResponse, X402ReceiptStatus, X402Resource, X402ResourceStatus, X402ResourceStatusResponse, atomicToMoneyUnits, createSecureMessageRequestSchema, isX402PaymentRequired, isX402ResourceStatus, moneyUnitsToAtomic, roundHalfToPositiveInfinity, secureMessageMetadataSchema, secureMessageSchema, validatePositiveAmountAtomic } from 'spaps-types';
4
4
 
5
5
  /**
6
6
  * Permission checking utilities for SPAPS SDK
@@ -336,6 +336,73 @@ interface EmailTemplatePreview {
336
336
  html: string;
337
337
  text?: string;
338
338
  }
339
+ type MarketingEventType = 'attribution_touch' | 'experiment_exposure';
340
+ type MarketingExperimentRecommendation = 'continue' | 'continue_srm_warning' | 'stop_for_winner';
341
+ type MarketingExperimentSrmStatus = 'pass' | 'warning' | 'not_applicable';
342
+ interface MarketingEventIngestRequest {
343
+ application_id?: string;
344
+ anon_id: string;
345
+ event_type: MarketingEventType;
346
+ experiment_id?: string | null;
347
+ variant_id?: string | null;
348
+ variant?: string | null;
349
+ metadata?: Record<string, unknown> | null;
350
+ dedupe_key?: string | null;
351
+ timestamp?: string | null;
352
+ observed_at?: string | null;
353
+ }
354
+ interface MarketingEventIngestResponse {
355
+ id: string;
356
+ application_id: string;
357
+ anon_id: string;
358
+ event_type: MarketingEventType;
359
+ experiment_id?: string | null;
360
+ variant?: string | null;
361
+ dedupe_key: string;
362
+ ts: string;
363
+ created_at: string;
364
+ }
365
+ interface MarketingExperimentVariantResult {
366
+ variant_id: string | null;
367
+ exposures: number;
368
+ conversions: number;
369
+ conversion_rate: number;
370
+ revenue: Record<string, number>;
371
+ }
372
+ interface MarketingExperimentMinSampleDecision {
373
+ met: boolean;
374
+ min_exposures_per_variant: number;
375
+ min_total_conversions: number;
376
+ observed_min_exposures: number;
377
+ observed_total_conversions: number;
378
+ }
379
+ interface MarketingExperimentSrmDecision {
380
+ status: MarketingExperimentSrmStatus;
381
+ expected_share?: number | null;
382
+ max_share_ratio?: number | null;
383
+ min_share_ratio?: number | null;
384
+ }
385
+ interface MarketingExperimentEffectDecision {
386
+ leader_variant_id?: string | null;
387
+ runner_up_variant_id?: string | null;
388
+ absolute_lift?: number | null;
389
+ relative_lift?: number | null;
390
+ }
391
+ interface MarketingExperimentDecision {
392
+ decisive: boolean;
393
+ recommendation: MarketingExperimentRecommendation;
394
+ winner_variant_id?: string | null;
395
+ reason_codes: string[];
396
+ min_sample: MarketingExperimentMinSampleDecision;
397
+ srm: MarketingExperimentSrmDecision;
398
+ effect: MarketingExperimentEffectDecision;
399
+ }
400
+ interface MarketingExperimentResultsResponse {
401
+ application_id: string;
402
+ experiment_id: string;
403
+ variants: MarketingExperimentVariantResult[];
404
+ decision: MarketingExperimentDecision;
405
+ }
339
406
  interface EntitlementListParams {
340
407
  /** Filter by entitlement key */
341
408
  key?: string;
@@ -350,6 +417,22 @@ interface EntitlementCheckResult {
350
417
  /** The matching entitlement, if any */
351
418
  entitlement?: Entitlement;
352
419
  }
420
+ interface CurrentUserProjectGrantListParams {
421
+ /** Filter to one project entitlement key, such as "pds.project.viewer" */
422
+ entitlementKey?: string;
423
+ /** Maximum number of project grants to return */
424
+ limit?: number;
425
+ /** Zero-based item offset */
426
+ offset?: number;
427
+ /** Opaque pagination cursor */
428
+ cursor?: string;
429
+ }
430
+ interface CurrentUserProjectAccessParams {
431
+ /** Project to check */
432
+ projectId: string;
433
+ /** Project entitlement key to check, such as "pds.project.viewer" */
434
+ entitlementKey: string;
435
+ }
353
436
  type SupportedIssueReportScope = Extract<IssueReportScope, 'mine'>;
354
437
  interface IssueReportListParams {
355
438
  status?: IssueReportStatus;
@@ -631,6 +714,7 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
631
714
  private headerProvider?;
632
715
  private unwrapApiResponse;
633
716
  private skillEvalMutationConfig;
717
+ private requireCurrentUserIdFromAccessToken;
634
718
  private isAxiosResponse;
635
719
  private isResponseLikeWithData;
636
720
  private isApiResponse;
@@ -708,10 +792,24 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
708
792
  * Note: For publishable key contexts, only `resource_type=user` is permitted.
709
793
  * Non-user resource types will return a 403 error (enforced server-side).
710
794
  *
711
- * @param resourceType - The resource type (e.g. "user", "company", "org", "system").
795
+ * @param resourceType - The resource type (e.g. "user", "company", "org", "system", "project").
712
796
  * @param resourceId - Optional specific resource ID.
713
797
  */
714
798
  listByResource: (resourceType: ResourceType, resourceId?: string) => Promise<Entitlement[]>;
799
+ /**
800
+ * List project grants for the authenticated user.
801
+ *
802
+ * Browser/publishable-key contexts are safe here because the server requires
803
+ * the JWT subject to match the path user id.
804
+ */
805
+ listCurrentUserProjects: (params?: CurrentUserProjectGrantListParams) => Promise<ListProjectGrantsResponse>;
806
+ /**
807
+ * Check whether the authenticated user has one project entitlement.
808
+ *
809
+ * The helper does not accept user/email overrides; publishable-key callers
810
+ * are scoped by the server to the JWT identity.
811
+ */
812
+ checkCurrentUserProjectAccess: (params: CurrentUserProjectAccessParams) => Promise<ProjectAccessCheckResponse>;
715
813
  };
716
814
  /**
717
815
  * Issue reporting namespace for authenticated end-user issue flows.
@@ -744,6 +842,12 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
744
842
  /**
745
843
  * Return a short-lived private attachment access URL.
746
844
  */
845
+ getAttachmentAccess: (attachmentId: string) => Promise<IssueReportAttachmentAccessResponse>;
846
+ /**
847
+ * Return a short-lived private attachment access URL.
848
+ *
849
+ * @deprecated Use getAttachmentAccess(attachmentId).
850
+ */
747
851
  getAttachmentAccessUrl: (attachmentId: string) => Promise<IssueReportAttachmentAccessResponse>;
748
852
  /**
749
853
  * Soft-delete one owned screenshot attachment.
@@ -761,6 +865,46 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
761
865
  * Reply to a resolved or ignored issue report and reopen the linked case.
762
866
  */
763
867
  reply: (issueReportId: string, payload: ReplyIssueReportRequest) => Promise<IssueReport>;
868
+ /**
869
+ * List the chronological, reporter-visible message thread for one owned
870
+ * issue report. Only active, reporter-visible rows are returned; retracted
871
+ * or superseded operator messages are excluded and raw audit payloads are
872
+ * never exposed. `needs_response` badges an open clarification request.
873
+ */
874
+ listMessages: (issueReportId: string) => Promise<ListIssueReportMessagesResponse>;
875
+ /**
876
+ * Submit a reporter clarification response on one owned issue report. This
877
+ * does NOT reopen a closed case (use `reply` for that). Idempotent on
878
+ * `idempotency_key`: same key + same body returns the existing message,
879
+ * same key + different body returns 409 ISSUE_REPORT_MESSAGE_CONFLICT.
880
+ */
881
+ submitMessage: (issueReportId: string, payload: CreateReporterMessageRequest) => Promise<IssueReportMessage>;
882
+ /**
883
+ * Triage-operator message commands. These require a secret key context and a
884
+ * triage role/capability; publishable browser contexts are rejected by the
885
+ * server. Provide the secret key as the SDK's API key.
886
+ */
887
+ triage: {
888
+ /**
889
+ * List all projection rows for an app-scoped report (including corrected
890
+ * rows) for authorized triage operators.
891
+ */
892
+ listMessages: (issueReportId: string) => Promise<ListIssueReportMessagesResponse>;
893
+ /**
894
+ * Post a clarification_request or final_response reporter-visible message
895
+ * as a triage operator. Idempotent on `idempotency_key`; a key reused with
896
+ * a different body returns 409. A final_response can drive a terminal
897
+ * lifecycle transition via `resolve_case`; the internal resolution_note is
898
+ * never auto-displayed to reporters.
899
+ */
900
+ postMessage: (issueReportId: string, payload: CreateOperatorMessageRequest) => Promise<IssueReportMessage>;
901
+ /**
902
+ * Retract an operator-authored message. The body is retained; the row's
903
+ * state is marked `retracted`. Retracting an already-retracted message is
904
+ * a safe no-op.
905
+ */
906
+ retractMessage: (issueReportId: string, messageId: string, payload?: RetractOperatorMessageRequest) => Promise<IssueReportMessage>;
907
+ };
764
908
  };
765
909
  /**
766
910
  * Application-scoped short links for browser apps that need stable public URLs.
@@ -770,6 +914,10 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
770
914
  * Create a short link owned by the authenticated user.
771
915
  */
772
916
  create: (payload: CreateAppLinkRequest) => Promise<AppLink>;
917
+ /**
918
+ * Update a short link owned by the authenticated user.
919
+ */
920
+ update: (username: string, slug: string, payload: UpdateAppLinkRequest) => Promise<AppLink>;
773
921
  /**
774
922
  * Resolve a public short link for the active application.
775
923
  */
@@ -777,6 +925,30 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
777
925
  track?: boolean;
778
926
  }) => Promise<AppLink>;
779
927
  };
928
+ /**
929
+ * Marketing events and experiment results.
930
+ *
931
+ * Use a publishable key for browser-side event emission. Use a secret key
932
+ * from a trusted server or agent process when reading experiment results.
933
+ */
934
+ marketing: {
935
+ /**
936
+ * Emit one anonymous attribution touch or experiment exposure.
937
+ */
938
+ emit: (payload: MarketingEventIngestRequest) => Promise<MarketingEventIngestResponse>;
939
+ /**
940
+ * Alias for emit(), matching the backend ingest endpoint language.
941
+ */
942
+ ingest: (payload: MarketingEventIngestRequest) => Promise<MarketingEventIngestResponse>;
943
+ /**
944
+ * Read per-variant experiment results and the conservative stop signal.
945
+ */
946
+ getExperimentResults: (experimentId: string) => Promise<MarketingExperimentResultsResponse>;
947
+ /**
948
+ * Short alias for getExperimentResults().
949
+ */
950
+ getResults: (experimentId: string) => Promise<MarketingExperimentResultsResponse>;
951
+ };
780
952
  private static envVar;
781
953
  constructor(config?: SPAPSConfig);
782
954
  /** Raw API request helper that returns an ApiResponse-like shape */
@@ -1154,6 +1326,8 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
1154
1326
  recordUsage(payload: UsageControlRecordRequest): Promise<UsageControlRecordResponse>;
1155
1327
  getUsageStatus(query: UsageControlStatusRequest): Promise<UsageControlStatusResponse>;
1156
1328
  listUsageHistory(query?: UsageControlHistoryRequest): Promise<UsageControlHistoryResponse>;
1329
+ emitMarketingEvent(payload: MarketingEventIngestRequest): Promise<MarketingEventIngestResponse>;
1330
+ getMarketingExperimentResults(experimentId: string): Promise<MarketingExperimentResultsResponse>;
1157
1331
  private createSecureMessage;
1158
1332
  private listSecureMessages;
1159
1333
  /**
@@ -1335,4 +1509,4 @@ declare function createServerClient(secretKey: string, options?: Omit<SPAPSConfi
1335
1509
  */
1336
1510
  declare function detectKeyType(key: string): ApiKeyType | null;
1337
1511
 
1338
- export { type AdminConfig, type ApiKeyType, type CheckoutLineItem, type CheckoutLineItemPriceData, type CreateCheckoutSessionPayload, type CreateSkillEvalCaseRequest, type CreateSkillEvalGovernanceSnapshotRequest, DEFAULT_ADMIN_ACCOUNTS, type EmailSendOptions, type EmailSendResult, type EmailTemplate, type EmailTemplatePreview, type EntitlementCheckResult, type EntitlementListParams, type FeatureContext, type FeatureDefinition, FeatureEvaluator, type HeaderProvider, type ImportSkillEvalGovernanceOutcomeRequest, type IssueReportAttachmentUploadOptions, type IssueReportListParams, type IssueReportStatusParams, type PermissionCheckResult, PermissionChecker, type RespondToSkillEvalReviewRequest, type RevealSkillEvalEvidenceRequest, RoleHierarchy, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, type SPAPSEnvelope, type SkillEvalAccessMode, type SkillEvalActorAccess, type SkillEvalCandidateInput, type SkillEvalCandidateResponse, type SkillEvalCasePolicy, type SkillEvalCaseResponse, type SkillEvalConfidence, type SkillEvalCreateOptions, type SkillEvalDisclosurePolicy, type SkillEvalEligibilitySource, type SkillEvalGovernanceOutcomeResult, type SkillEvalGovernancePurpose, type SkillEvalGovernanceSnapshotResult, type SkillEvalInsight, type SkillEvalInsightsResponse, type SkillEvalModelEffort, type SkillEvalMutationOptions, type SkillEvalPosterResponse, type SkillEvalPosterResponseResult, type SkillEvalRevealField, type SkillEvalRevealResult, type SkillEvalReviewMarkInput, type SkillEvalReviewMarkKind, type SkillEvalReviewResponse, type SkillEvalReviewRoom, type SkillEvalReviewerEligibilityInput, type SkillEvalRewardEvent, type SubmitSkillEvalReviewRequest, type TemplateVariable, TokenManager, WalletUtils, WebSocketAuthHelper, type WebSocketAuthHelperConfig, type X402ExecuteActionOptions, X402PaymentRequiredSDKError, type X402ReceiptListParams, type X402VerifyHandoffOptions, canAccessAdmin, createBrowserClient, createPermissionChecker, createServerClient, SPAPSClient as default, defaultPermissionChecker, detectKeyType, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, isEnvelope, isErrorEnvelope, isSuccessEnvelope, unwrapEnvelope, unwrapNestedData, verifyCryptoWebhookSignature };
1512
+ export { type AdminConfig, type ApiKeyType, type CheckoutLineItem, type CheckoutLineItemPriceData, type CreateCheckoutSessionPayload, type CreateSkillEvalCaseRequest, type CreateSkillEvalGovernanceSnapshotRequest, type CurrentUserProjectAccessParams, type CurrentUserProjectGrantListParams, DEFAULT_ADMIN_ACCOUNTS, type EmailSendOptions, type EmailSendResult, type EmailTemplate, type EmailTemplatePreview, type EntitlementCheckResult, type EntitlementListParams, type FeatureContext, type FeatureDefinition, FeatureEvaluator, type HeaderProvider, type ImportSkillEvalGovernanceOutcomeRequest, type IssueReportAttachmentUploadOptions, type IssueReportListParams, type IssueReportStatusParams, type MarketingEventIngestRequest, type MarketingEventIngestResponse, type MarketingEventType, type MarketingExperimentDecision, type MarketingExperimentEffectDecision, type MarketingExperimentMinSampleDecision, type MarketingExperimentRecommendation, type MarketingExperimentResultsResponse, type MarketingExperimentSrmDecision, type MarketingExperimentSrmStatus, type MarketingExperimentVariantResult, type PermissionCheckResult, PermissionChecker, type RespondToSkillEvalReviewRequest, type RevealSkillEvalEvidenceRequest, RoleHierarchy, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, type SPAPSEnvelope, type SkillEvalAccessMode, type SkillEvalActorAccess, type SkillEvalCandidateInput, type SkillEvalCandidateResponse, type SkillEvalCasePolicy, type SkillEvalCaseResponse, type SkillEvalConfidence, type SkillEvalCreateOptions, type SkillEvalDisclosurePolicy, type SkillEvalEligibilitySource, type SkillEvalGovernanceOutcomeResult, type SkillEvalGovernancePurpose, type SkillEvalGovernanceSnapshotResult, type SkillEvalInsight, type SkillEvalInsightsResponse, type SkillEvalModelEffort, type SkillEvalMutationOptions, type SkillEvalPosterResponse, type SkillEvalPosterResponseResult, type SkillEvalRevealField, type SkillEvalRevealResult, type SkillEvalReviewMarkInput, type SkillEvalReviewMarkKind, type SkillEvalReviewResponse, type SkillEvalReviewRoom, type SkillEvalReviewerEligibilityInput, type SkillEvalRewardEvent, type SubmitSkillEvalReviewRequest, type TemplateVariable, TokenManager, WalletUtils, WebSocketAuthHelper, type WebSocketAuthHelperConfig, type X402ExecuteActionOptions, X402PaymentRequiredSDKError, type X402ReceiptListParams, type X402VerifyHandoffOptions, canAccessAdmin, createBrowserClient, createPermissionChecker, createServerClient, SPAPSClient as default, defaultPermissionChecker, detectKeyType, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, isEnvelope, isErrorEnvelope, isSuccessEnvelope, unwrapEnvelope, unwrapNestedData, verifyCryptoWebhookSignature };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as spaps_types from 'spaps-types';
2
- import { ResourceType, Entitlement, CreateProductRequest, Product, UpdateProductRequest, CreatePriceRequest, Price, ProductSyncResult, CryptoReconcileRequest, CreateSecureMessageRequest, SecureMessage, IssueReportScope, IssueReportStatusResult, IssueReportStatus, IssueReportListResult, IssueReport, CreateIssueReportRequest, IssueReportAttachmentOut, ListIssueReportAttachmentsResponse, IssueReportAttachmentAccessResponse, IssueReportingVoiceTokenResult, UpdateIssueReportRequest, ReplyIssueReportRequest, CreateAppLinkRequest, AppLink, AuthResponse, User as User$1, CreateCryptoInvoiceRequest, CryptoInvoiceStatusSnapshot, CheckoutSession, X402ResourceStatusResponse, X402ActionResponse, X402ReceiptResponse, X402ReceiptListResponse, X402HandoffVerification, DayrateAvailabilityResponse, DayrateBookingRequest, DayrateBookingResponse, DayrateMultiBookingRequest, DayrateMultiBookingResponse, DayrateX402BookingRequest, DayrateX402BookingResponse, DayrateCheckoutStatusResponse, UsageControlFeaturesResponse, UsageControlStatusRequest, UsageControlStatusResponse, UsageControlAuthorizeRequest, UsageControlAuthorizeResponse, UsageControlRecordRequest, UsageControlRecordResponse, UsageControlHistoryRequest, UsageControlHistoryResponse, Subscription, VerifyCryptoWebhookSignatureOptions } from 'spaps-types';
3
- export { AdminPermission, AdminRole, AdminUser, ApiResponse, AppLink, AuthResponse, CheckoutSession, CreateAppLinkRequest, CreateCryptoInvoiceRequest, CreateIssueReportRequest, CreatePriceRequest, CreateProductRequest, CreateSecureMessageInput, CreateSecureMessageRequest, CryptoInvoice, CryptoInvoiceResponse, CryptoInvoiceStatusSnapshot, CryptoReconcileRequest, DayrateAvailabilityResponse, DayrateAvailableSlot, DayrateBookingRequest, DayrateBookingResponse, DayrateCheckoutStatus, DayrateCheckoutStatusBooking, DayrateCheckoutStatusResponse, DayrateDayOfWeek, DayrateMultiBookingRequest, DayrateMultiBookingResponse, DayratePriceBreakdown, DayrateSlotType, DayrateX402BookingRequest, DayrateX402BookingResponse, Entitlement, IssueReport, IssueReportAttachmentAccessResponse, IssueReportAttachmentOut, IssueReportListResult, IssueReportStatus, IssueReportStatusResult, IssueReportTarget, IssueReportingInputMode, IssueReportingVoiceProvider, IssueReportingVoiceTokenResult, LinkedIssueReportCase, ListIssueReportAttachmentsResponse, Price, Product, ProductSyncResult, ReplyIssueReportRequest, ResourceType, SecureMessage, SecureMessageOutput, Subscription, TokenPair, UpdateIssueReportRequest, UpdateProductRequest, UsageControlAuthorizeRequest, UsageControlAuthorizeResponse, UsageControlDecision, UsageControlDimensions, UsageControlError, UsageControlErrorCode, UsageControlFeaturesResponse, UsageControlHistoryRequest, UsageControlHistoryResponse, UsageControlLedgerEvent, UsageControlRecordRequest, UsageControlRecordResponse, UsageControlRecordStatus, UsageControlStatusRequest, UsageControlStatusResponse, User, UserProfile, UserRole, UserWallet, VerifyCryptoWebhookSignatureOptions, X402ActionFreeResponse, X402ActionOutcome, X402ActionPendingResponse, X402ActionReplayedResponse, X402ActionResponse, X402ActionSettledResponse, X402ExecuteActionRequest, X402HandoffAuthorization, X402HandoffVerification, X402HandoffVerifyRequest, X402PaymentAccept, X402PaymentRequirement, X402ProjectionStatus, X402Receipt, X402ReceiptListResponse, X402ReceiptResponse, X402ReceiptStatus, X402Resource, X402ResourceStatus, X402ResourceStatusResponse, atomicToMoneyUnits, createSecureMessageRequestSchema, isX402PaymentRequired, isX402ResourceStatus, moneyUnitsToAtomic, roundHalfToPositiveInfinity, secureMessageMetadataSchema, secureMessageSchema, validatePositiveAmountAtomic } from 'spaps-types';
2
+ import { ResourceType, Entitlement, CreateProductRequest, Product, UpdateProductRequest, CreatePriceRequest, Price, ProductSyncResult, CryptoReconcileRequest, CreateSecureMessageRequest, SecureMessage, ListProjectGrantsResponse, ProjectAccessCheckResponse, IssueReportScope, IssueReportStatusResult, IssueReportStatus, IssueReportListResult, IssueReport, CreateIssueReportRequest, IssueReportAttachmentOut, ListIssueReportAttachmentsResponse, IssueReportAttachmentAccessResponse, IssueReportingVoiceTokenResult, UpdateIssueReportRequest, ReplyIssueReportRequest, ListIssueReportMessagesResponse, CreateReporterMessageRequest, IssueReportMessage, CreateOperatorMessageRequest, RetractOperatorMessageRequest, CreateAppLinkRequest, AppLink, UpdateAppLinkRequest, AuthResponse, User as User$1, CreateCryptoInvoiceRequest, CryptoInvoiceStatusSnapshot, CheckoutSession, X402ResourceStatusResponse, X402ActionResponse, X402ReceiptResponse, X402ReceiptListResponse, X402HandoffVerification, DayrateAvailabilityResponse, DayrateBookingRequest, DayrateBookingResponse, DayrateMultiBookingRequest, DayrateMultiBookingResponse, DayrateX402BookingRequest, DayrateX402BookingResponse, DayrateCheckoutStatusResponse, UsageControlFeaturesResponse, UsageControlStatusRequest, UsageControlStatusResponse, UsageControlAuthorizeRequest, UsageControlAuthorizeResponse, UsageControlRecordRequest, UsageControlRecordResponse, UsageControlHistoryRequest, UsageControlHistoryResponse, Subscription, VerifyCryptoWebhookSignatureOptions } from 'spaps-types';
3
+ export { AdminPermission, AdminRole, AdminUser, ApiResponse, AppLink, AuthResponse, CheckoutSession, CreateAppLinkRequest, CreateCryptoInvoiceRequest, CreateIssueReportRequest, CreateOperatorMessageRequest, CreatePriceRequest, CreateProductRequest, CreateReporterMessageRequest, CreateSecureMessageInput, CreateSecureMessageRequest, CryptoInvoice, CryptoInvoiceResponse, CryptoInvoiceStatusSnapshot, CryptoReconcileRequest, DayrateAvailabilityResponse, DayrateAvailableSlot, DayrateBookingRequest, DayrateBookingResponse, DayrateCheckoutStatus, DayrateCheckoutStatusBooking, DayrateCheckoutStatusResponse, DayrateDayOfWeek, DayrateMultiBookingRequest, DayrateMultiBookingResponse, DayratePriceBreakdown, DayrateSlotType, DayrateX402BookingRequest, DayrateX402BookingResponse, Entitlement, IssueReport, IssueReportAttachmentAccessResponse, IssueReportAttachmentOut, IssueReportListResult, IssueReportMessage, IssueReportStatus, IssueReportStatusResult, IssueReportTarget, IssueReportingInputMode, IssueReportingVoiceProvider, IssueReportingVoiceTokenResult, LinkedIssueReportCase, ListIssueReportAttachmentsResponse, ListIssueReportMessagesResponse, ListProjectGrantsResponse, Price, Product, ProductSyncResult, ProjectAccessCheckResponse, ProjectGrant, ProjectGrantStatus, ReplyIssueReportRequest, ResourceType, RetractOperatorMessageRequest, SecureMessage, SecureMessageOutput, Subscription, TokenPair, UpdateAppLinkRequest, UpdateIssueReportRequest, UpdateProductRequest, UsageControlAuthorizeRequest, UsageControlAuthorizeResponse, UsageControlDecision, UsageControlDimensions, UsageControlError, UsageControlErrorCode, UsageControlFeaturesResponse, UsageControlHistoryRequest, UsageControlHistoryResponse, UsageControlLedgerEvent, UsageControlRecordRequest, UsageControlRecordResponse, UsageControlRecordStatus, UsageControlStatusRequest, UsageControlStatusResponse, User, UserProfile, UserRole, UserWallet, VerifyCryptoWebhookSignatureOptions, X402ActionFreeResponse, X402ActionOutcome, X402ActionPendingResponse, X402ActionReplayedResponse, X402ActionResponse, X402ActionSettledResponse, X402ExecuteActionRequest, X402HandoffAuthorization, X402HandoffVerification, X402HandoffVerifyRequest, X402PaymentAccept, X402PaymentRequirement, X402ProjectionStatus, X402Receipt, X402ReceiptListResponse, X402ReceiptResponse, X402ReceiptStatus, X402Resource, X402ResourceStatus, X402ResourceStatusResponse, atomicToMoneyUnits, createSecureMessageRequestSchema, isX402PaymentRequired, isX402ResourceStatus, moneyUnitsToAtomic, roundHalfToPositiveInfinity, secureMessageMetadataSchema, secureMessageSchema, validatePositiveAmountAtomic } from 'spaps-types';
4
4
 
5
5
  /**
6
6
  * Permission checking utilities for SPAPS SDK
@@ -336,6 +336,73 @@ interface EmailTemplatePreview {
336
336
  html: string;
337
337
  text?: string;
338
338
  }
339
+ type MarketingEventType = 'attribution_touch' | 'experiment_exposure';
340
+ type MarketingExperimentRecommendation = 'continue' | 'continue_srm_warning' | 'stop_for_winner';
341
+ type MarketingExperimentSrmStatus = 'pass' | 'warning' | 'not_applicable';
342
+ interface MarketingEventIngestRequest {
343
+ application_id?: string;
344
+ anon_id: string;
345
+ event_type: MarketingEventType;
346
+ experiment_id?: string | null;
347
+ variant_id?: string | null;
348
+ variant?: string | null;
349
+ metadata?: Record<string, unknown> | null;
350
+ dedupe_key?: string | null;
351
+ timestamp?: string | null;
352
+ observed_at?: string | null;
353
+ }
354
+ interface MarketingEventIngestResponse {
355
+ id: string;
356
+ application_id: string;
357
+ anon_id: string;
358
+ event_type: MarketingEventType;
359
+ experiment_id?: string | null;
360
+ variant?: string | null;
361
+ dedupe_key: string;
362
+ ts: string;
363
+ created_at: string;
364
+ }
365
+ interface MarketingExperimentVariantResult {
366
+ variant_id: string | null;
367
+ exposures: number;
368
+ conversions: number;
369
+ conversion_rate: number;
370
+ revenue: Record<string, number>;
371
+ }
372
+ interface MarketingExperimentMinSampleDecision {
373
+ met: boolean;
374
+ min_exposures_per_variant: number;
375
+ min_total_conversions: number;
376
+ observed_min_exposures: number;
377
+ observed_total_conversions: number;
378
+ }
379
+ interface MarketingExperimentSrmDecision {
380
+ status: MarketingExperimentSrmStatus;
381
+ expected_share?: number | null;
382
+ max_share_ratio?: number | null;
383
+ min_share_ratio?: number | null;
384
+ }
385
+ interface MarketingExperimentEffectDecision {
386
+ leader_variant_id?: string | null;
387
+ runner_up_variant_id?: string | null;
388
+ absolute_lift?: number | null;
389
+ relative_lift?: number | null;
390
+ }
391
+ interface MarketingExperimentDecision {
392
+ decisive: boolean;
393
+ recommendation: MarketingExperimentRecommendation;
394
+ winner_variant_id?: string | null;
395
+ reason_codes: string[];
396
+ min_sample: MarketingExperimentMinSampleDecision;
397
+ srm: MarketingExperimentSrmDecision;
398
+ effect: MarketingExperimentEffectDecision;
399
+ }
400
+ interface MarketingExperimentResultsResponse {
401
+ application_id: string;
402
+ experiment_id: string;
403
+ variants: MarketingExperimentVariantResult[];
404
+ decision: MarketingExperimentDecision;
405
+ }
339
406
  interface EntitlementListParams {
340
407
  /** Filter by entitlement key */
341
408
  key?: string;
@@ -350,6 +417,22 @@ interface EntitlementCheckResult {
350
417
  /** The matching entitlement, if any */
351
418
  entitlement?: Entitlement;
352
419
  }
420
+ interface CurrentUserProjectGrantListParams {
421
+ /** Filter to one project entitlement key, such as "pds.project.viewer" */
422
+ entitlementKey?: string;
423
+ /** Maximum number of project grants to return */
424
+ limit?: number;
425
+ /** Zero-based item offset */
426
+ offset?: number;
427
+ /** Opaque pagination cursor */
428
+ cursor?: string;
429
+ }
430
+ interface CurrentUserProjectAccessParams {
431
+ /** Project to check */
432
+ projectId: string;
433
+ /** Project entitlement key to check, such as "pds.project.viewer" */
434
+ entitlementKey: string;
435
+ }
353
436
  type SupportedIssueReportScope = Extract<IssueReportScope, 'mine'>;
354
437
  interface IssueReportListParams {
355
438
  status?: IssueReportStatus;
@@ -631,6 +714,7 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
631
714
  private headerProvider?;
632
715
  private unwrapApiResponse;
633
716
  private skillEvalMutationConfig;
717
+ private requireCurrentUserIdFromAccessToken;
634
718
  private isAxiosResponse;
635
719
  private isResponseLikeWithData;
636
720
  private isApiResponse;
@@ -708,10 +792,24 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
708
792
  * Note: For publishable key contexts, only `resource_type=user` is permitted.
709
793
  * Non-user resource types will return a 403 error (enforced server-side).
710
794
  *
711
- * @param resourceType - The resource type (e.g. "user", "company", "org", "system").
795
+ * @param resourceType - The resource type (e.g. "user", "company", "org", "system", "project").
712
796
  * @param resourceId - Optional specific resource ID.
713
797
  */
714
798
  listByResource: (resourceType: ResourceType, resourceId?: string) => Promise<Entitlement[]>;
799
+ /**
800
+ * List project grants for the authenticated user.
801
+ *
802
+ * Browser/publishable-key contexts are safe here because the server requires
803
+ * the JWT subject to match the path user id.
804
+ */
805
+ listCurrentUserProjects: (params?: CurrentUserProjectGrantListParams) => Promise<ListProjectGrantsResponse>;
806
+ /**
807
+ * Check whether the authenticated user has one project entitlement.
808
+ *
809
+ * The helper does not accept user/email overrides; publishable-key callers
810
+ * are scoped by the server to the JWT identity.
811
+ */
812
+ checkCurrentUserProjectAccess: (params: CurrentUserProjectAccessParams) => Promise<ProjectAccessCheckResponse>;
715
813
  };
716
814
  /**
717
815
  * Issue reporting namespace for authenticated end-user issue flows.
@@ -744,6 +842,12 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
744
842
  /**
745
843
  * Return a short-lived private attachment access URL.
746
844
  */
845
+ getAttachmentAccess: (attachmentId: string) => Promise<IssueReportAttachmentAccessResponse>;
846
+ /**
847
+ * Return a short-lived private attachment access URL.
848
+ *
849
+ * @deprecated Use getAttachmentAccess(attachmentId).
850
+ */
747
851
  getAttachmentAccessUrl: (attachmentId: string) => Promise<IssueReportAttachmentAccessResponse>;
748
852
  /**
749
853
  * Soft-delete one owned screenshot attachment.
@@ -761,6 +865,46 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
761
865
  * Reply to a resolved or ignored issue report and reopen the linked case.
762
866
  */
763
867
  reply: (issueReportId: string, payload: ReplyIssueReportRequest) => Promise<IssueReport>;
868
+ /**
869
+ * List the chronological, reporter-visible message thread for one owned
870
+ * issue report. Only active, reporter-visible rows are returned; retracted
871
+ * or superseded operator messages are excluded and raw audit payloads are
872
+ * never exposed. `needs_response` badges an open clarification request.
873
+ */
874
+ listMessages: (issueReportId: string) => Promise<ListIssueReportMessagesResponse>;
875
+ /**
876
+ * Submit a reporter clarification response on one owned issue report. This
877
+ * does NOT reopen a closed case (use `reply` for that). Idempotent on
878
+ * `idempotency_key`: same key + same body returns the existing message,
879
+ * same key + different body returns 409 ISSUE_REPORT_MESSAGE_CONFLICT.
880
+ */
881
+ submitMessage: (issueReportId: string, payload: CreateReporterMessageRequest) => Promise<IssueReportMessage>;
882
+ /**
883
+ * Triage-operator message commands. These require a secret key context and a
884
+ * triage role/capability; publishable browser contexts are rejected by the
885
+ * server. Provide the secret key as the SDK's API key.
886
+ */
887
+ triage: {
888
+ /**
889
+ * List all projection rows for an app-scoped report (including corrected
890
+ * rows) for authorized triage operators.
891
+ */
892
+ listMessages: (issueReportId: string) => Promise<ListIssueReportMessagesResponse>;
893
+ /**
894
+ * Post a clarification_request or final_response reporter-visible message
895
+ * as a triage operator. Idempotent on `idempotency_key`; a key reused with
896
+ * a different body returns 409. A final_response can drive a terminal
897
+ * lifecycle transition via `resolve_case`; the internal resolution_note is
898
+ * never auto-displayed to reporters.
899
+ */
900
+ postMessage: (issueReportId: string, payload: CreateOperatorMessageRequest) => Promise<IssueReportMessage>;
901
+ /**
902
+ * Retract an operator-authored message. The body is retained; the row's
903
+ * state is marked `retracted`. Retracting an already-retracted message is
904
+ * a safe no-op.
905
+ */
906
+ retractMessage: (issueReportId: string, messageId: string, payload?: RetractOperatorMessageRequest) => Promise<IssueReportMessage>;
907
+ };
764
908
  };
765
909
  /**
766
910
  * Application-scoped short links for browser apps that need stable public URLs.
@@ -770,6 +914,10 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
770
914
  * Create a short link owned by the authenticated user.
771
915
  */
772
916
  create: (payload: CreateAppLinkRequest) => Promise<AppLink>;
917
+ /**
918
+ * Update a short link owned by the authenticated user.
919
+ */
920
+ update: (username: string, slug: string, payload: UpdateAppLinkRequest) => Promise<AppLink>;
773
921
  /**
774
922
  * Resolve a public short link for the active application.
775
923
  */
@@ -777,6 +925,30 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
777
925
  track?: boolean;
778
926
  }) => Promise<AppLink>;
779
927
  };
928
+ /**
929
+ * Marketing events and experiment results.
930
+ *
931
+ * Use a publishable key for browser-side event emission. Use a secret key
932
+ * from a trusted server or agent process when reading experiment results.
933
+ */
934
+ marketing: {
935
+ /**
936
+ * Emit one anonymous attribution touch or experiment exposure.
937
+ */
938
+ emit: (payload: MarketingEventIngestRequest) => Promise<MarketingEventIngestResponse>;
939
+ /**
940
+ * Alias for emit(), matching the backend ingest endpoint language.
941
+ */
942
+ ingest: (payload: MarketingEventIngestRequest) => Promise<MarketingEventIngestResponse>;
943
+ /**
944
+ * Read per-variant experiment results and the conservative stop signal.
945
+ */
946
+ getExperimentResults: (experimentId: string) => Promise<MarketingExperimentResultsResponse>;
947
+ /**
948
+ * Short alias for getExperimentResults().
949
+ */
950
+ getResults: (experimentId: string) => Promise<MarketingExperimentResultsResponse>;
951
+ };
780
952
  private static envVar;
781
953
  constructor(config?: SPAPSConfig);
782
954
  /** Raw API request helper that returns an ApiResponse-like shape */
@@ -1154,6 +1326,8 @@ declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Re
1154
1326
  recordUsage(payload: UsageControlRecordRequest): Promise<UsageControlRecordResponse>;
1155
1327
  getUsageStatus(query: UsageControlStatusRequest): Promise<UsageControlStatusResponse>;
1156
1328
  listUsageHistory(query?: UsageControlHistoryRequest): Promise<UsageControlHistoryResponse>;
1329
+ emitMarketingEvent(payload: MarketingEventIngestRequest): Promise<MarketingEventIngestResponse>;
1330
+ getMarketingExperimentResults(experimentId: string): Promise<MarketingExperimentResultsResponse>;
1157
1331
  private createSecureMessage;
1158
1332
  private listSecureMessages;
1159
1333
  /**
@@ -1335,4 +1509,4 @@ declare function createServerClient(secretKey: string, options?: Omit<SPAPSConfi
1335
1509
  */
1336
1510
  declare function detectKeyType(key: string): ApiKeyType | null;
1337
1511
 
1338
- export { type AdminConfig, type ApiKeyType, type CheckoutLineItem, type CheckoutLineItemPriceData, type CreateCheckoutSessionPayload, type CreateSkillEvalCaseRequest, type CreateSkillEvalGovernanceSnapshotRequest, DEFAULT_ADMIN_ACCOUNTS, type EmailSendOptions, type EmailSendResult, type EmailTemplate, type EmailTemplatePreview, type EntitlementCheckResult, type EntitlementListParams, type FeatureContext, type FeatureDefinition, FeatureEvaluator, type HeaderProvider, type ImportSkillEvalGovernanceOutcomeRequest, type IssueReportAttachmentUploadOptions, type IssueReportListParams, type IssueReportStatusParams, type PermissionCheckResult, PermissionChecker, type RespondToSkillEvalReviewRequest, type RevealSkillEvalEvidenceRequest, RoleHierarchy, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, type SPAPSEnvelope, type SkillEvalAccessMode, type SkillEvalActorAccess, type SkillEvalCandidateInput, type SkillEvalCandidateResponse, type SkillEvalCasePolicy, type SkillEvalCaseResponse, type SkillEvalConfidence, type SkillEvalCreateOptions, type SkillEvalDisclosurePolicy, type SkillEvalEligibilitySource, type SkillEvalGovernanceOutcomeResult, type SkillEvalGovernancePurpose, type SkillEvalGovernanceSnapshotResult, type SkillEvalInsight, type SkillEvalInsightsResponse, type SkillEvalModelEffort, type SkillEvalMutationOptions, type SkillEvalPosterResponse, type SkillEvalPosterResponseResult, type SkillEvalRevealField, type SkillEvalRevealResult, type SkillEvalReviewMarkInput, type SkillEvalReviewMarkKind, type SkillEvalReviewResponse, type SkillEvalReviewRoom, type SkillEvalReviewerEligibilityInput, type SkillEvalRewardEvent, type SubmitSkillEvalReviewRequest, type TemplateVariable, TokenManager, WalletUtils, WebSocketAuthHelper, type WebSocketAuthHelperConfig, type X402ExecuteActionOptions, X402PaymentRequiredSDKError, type X402ReceiptListParams, type X402VerifyHandoffOptions, canAccessAdmin, createBrowserClient, createPermissionChecker, createServerClient, SPAPSClient as default, defaultPermissionChecker, detectKeyType, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, isEnvelope, isErrorEnvelope, isSuccessEnvelope, unwrapEnvelope, unwrapNestedData, verifyCryptoWebhookSignature };
1512
+ export { type AdminConfig, type ApiKeyType, type CheckoutLineItem, type CheckoutLineItemPriceData, type CreateCheckoutSessionPayload, type CreateSkillEvalCaseRequest, type CreateSkillEvalGovernanceSnapshotRequest, type CurrentUserProjectAccessParams, type CurrentUserProjectGrantListParams, DEFAULT_ADMIN_ACCOUNTS, type EmailSendOptions, type EmailSendResult, type EmailTemplate, type EmailTemplatePreview, type EntitlementCheckResult, type EntitlementListParams, type FeatureContext, type FeatureDefinition, FeatureEvaluator, type HeaderProvider, type ImportSkillEvalGovernanceOutcomeRequest, type IssueReportAttachmentUploadOptions, type IssueReportListParams, type IssueReportStatusParams, type MarketingEventIngestRequest, type MarketingEventIngestResponse, type MarketingEventType, type MarketingExperimentDecision, type MarketingExperimentEffectDecision, type MarketingExperimentMinSampleDecision, type MarketingExperimentRecommendation, type MarketingExperimentResultsResponse, type MarketingExperimentSrmDecision, type MarketingExperimentSrmStatus, type MarketingExperimentVariantResult, type PermissionCheckResult, PermissionChecker, type RespondToSkillEvalReviewRequest, type RevealSkillEvalEvidenceRequest, RoleHierarchy, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, type SPAPSEnvelope, type SkillEvalAccessMode, type SkillEvalActorAccess, type SkillEvalCandidateInput, type SkillEvalCandidateResponse, type SkillEvalCasePolicy, type SkillEvalCaseResponse, type SkillEvalConfidence, type SkillEvalCreateOptions, type SkillEvalDisclosurePolicy, type SkillEvalEligibilitySource, type SkillEvalGovernanceOutcomeResult, type SkillEvalGovernancePurpose, type SkillEvalGovernanceSnapshotResult, type SkillEvalInsight, type SkillEvalInsightsResponse, type SkillEvalModelEffort, type SkillEvalMutationOptions, type SkillEvalPosterResponse, type SkillEvalPosterResponseResult, type SkillEvalRevealField, type SkillEvalRevealResult, type SkillEvalReviewMarkInput, type SkillEvalReviewMarkKind, type SkillEvalReviewResponse, type SkillEvalReviewRoom, type SkillEvalReviewerEligibilityInput, type SkillEvalRewardEvent, type SubmitSkillEvalReviewRequest, type TemplateVariable, TokenManager, WalletUtils, WebSocketAuthHelper, type WebSocketAuthHelperConfig, type X402ExecuteActionOptions, X402PaymentRequiredSDKError, type X402ReceiptListParams, type X402VerifyHandoffOptions, canAccessAdmin, createBrowserClient, createPermissionChecker, createServerClient, SPAPSClient as default, defaultPermissionChecker, detectKeyType, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, isEnvelope, isErrorEnvelope, isSuccessEnvelope, unwrapEnvelope, unwrapNestedData, verifyCryptoWebhookSignature };
package/dist/index.js CHANGED
@@ -563,6 +563,17 @@ var SPAPSClient = class _SPAPSClient {
563
563
  }
564
564
  return { headers: { "If-Match": String(ifMatch) } };
565
565
  }
566
+ requireCurrentUserIdFromAccessToken() {
567
+ if (!this.accessToken) {
568
+ throw new Error("Authentication required. Please authenticate first.");
569
+ }
570
+ const payload = TokenManager.decodePayload(this.accessToken);
571
+ const userId = payload?.user_id ?? payload?.sub;
572
+ if (typeof userId !== "string" || userId.length === 0) {
573
+ throw new Error("Current user id not found in access token.");
574
+ }
575
+ return userId;
576
+ }
566
577
  isAxiosResponse(value) {
567
578
  if (!value || typeof value !== "object") {
568
579
  return false;
@@ -672,7 +683,7 @@ var SPAPSClient = class _SPAPSClient {
672
683
  * @param userId - Optional user ID override (secret key contexts only).
673
684
  */
674
685
  check: async (key, userId) => {
675
- const q = new URLSearchParams({ key });
686
+ const q = new URLSearchParams({ entitlement_key: key });
676
687
  if (userId) q.append("user_id", userId);
677
688
  const res = await this.client.get(
678
689
  `/api/entitlements/check?${q.toString()}`,
@@ -686,7 +697,7 @@ var SPAPSClient = class _SPAPSClient {
686
697
  * Note: For publishable key contexts, only `resource_type=user` is permitted.
687
698
  * Non-user resource types will return a 403 error (enforced server-side).
688
699
  *
689
- * @param resourceType - The resource type (e.g. "user", "company", "org", "system").
700
+ * @param resourceType - The resource type (e.g. "user", "company", "org", "system", "project").
690
701
  * @param resourceId - Optional specific resource ID.
691
702
  */
692
703
  listByResource: async (resourceType, resourceId) => {
@@ -701,6 +712,46 @@ var SPAPSClient = class _SPAPSClient {
701
712
  return payload.entitlements;
702
713
  }
703
714
  return payload;
715
+ },
716
+ /**
717
+ * List project grants for the authenticated user.
718
+ *
719
+ * Browser/publishable-key contexts are safe here because the server requires
720
+ * the JWT subject to match the path user id.
721
+ */
722
+ listCurrentUserProjects: async (params) => {
723
+ const userId = this.requireCurrentUserIdFromAccessToken();
724
+ const q = new URLSearchParams();
725
+ if (params?.entitlementKey) q.append("entitlement_key", params.entitlementKey);
726
+ if (params?.limit !== void 0) q.append("limit", String(params.limit));
727
+ if (params?.offset !== void 0) q.append("offset", String(params.offset));
728
+ if (params?.cursor) q.append("cursor", params.cursor);
729
+ const qs = q.toString();
730
+ const res = await this.client.get(
731
+ `/api/project-grants/user/${encodeURIComponent(userId)}/projects${qs ? `?${qs}` : ""}`,
732
+ { headers: { Authorization: `Bearer ${this.accessToken}` } }
733
+ );
734
+ return this.unwrapApiResponse(res, "Failed to list current user project grants");
735
+ },
736
+ /**
737
+ * Check whether the authenticated user has one project entitlement.
738
+ *
739
+ * The helper does not accept user/email overrides; publishable-key callers
740
+ * are scoped by the server to the JWT identity.
741
+ */
742
+ checkCurrentUserProjectAccess: async (params) => {
743
+ if (!this.accessToken) {
744
+ throw new Error("Authentication required. Please authenticate first.");
745
+ }
746
+ const q = new URLSearchParams({
747
+ project_id: params.projectId,
748
+ entitlement_key: params.entitlementKey
749
+ });
750
+ const res = await this.client.get(
751
+ `/api/project-grants/check?${q.toString()}`,
752
+ { headers: { Authorization: `Bearer ${this.accessToken}` } }
753
+ );
754
+ return this.unwrapApiResponse(res, "Failed to check current user project access");
704
755
  }
705
756
  };
706
757
  /**
@@ -793,7 +844,7 @@ var SPAPSClient = class _SPAPSClient {
793
844
  /**
794
845
  * Return a short-lived private attachment access URL.
795
846
  */
796
- getAttachmentAccessUrl: async (attachmentId) => {
847
+ getAttachmentAccess: async (attachmentId) => {
797
848
  const res = await this.client.get(
798
849
  `/api/v1/issue-reports/attachments/${encodeURIComponent(attachmentId)}/access`,
799
850
  this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
@@ -803,6 +854,14 @@ var SPAPSClient = class _SPAPSClient {
803
854
  "Failed to get issue report attachment access URL"
804
855
  );
805
856
  },
857
+ /**
858
+ * Return a short-lived private attachment access URL.
859
+ *
860
+ * @deprecated Use getAttachmentAccess(attachmentId).
861
+ */
862
+ getAttachmentAccessUrl: async (attachmentId) => {
863
+ return this.issueReporting.getAttachmentAccess(attachmentId);
864
+ },
806
865
  /**
807
866
  * Soft-delete one owned screenshot attachment.
808
867
  */
@@ -844,6 +903,82 @@ var SPAPSClient = class _SPAPSClient {
844
903
  this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
845
904
  );
846
905
  return this.unwrapApiResponse(res, "Failed to reply to issue report");
906
+ },
907
+ /**
908
+ * List the chronological, reporter-visible message thread for one owned
909
+ * issue report. Only active, reporter-visible rows are returned; retracted
910
+ * or superseded operator messages are excluded and raw audit payloads are
911
+ * never exposed. `needs_response` badges an open clarification request.
912
+ */
913
+ listMessages: async (issueReportId) => {
914
+ const res = await this.client.get(
915
+ `/api/v1/issue-reports/${issueReportId}/messages`,
916
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
917
+ );
918
+ return this.unwrapApiResponse(res, "Failed to list issue report messages");
919
+ },
920
+ /**
921
+ * Submit a reporter clarification response on one owned issue report. This
922
+ * does NOT reopen a closed case (use `reply` for that). Idempotent on
923
+ * `idempotency_key`: same key + same body returns the existing message,
924
+ * same key + different body returns 409 ISSUE_REPORT_MESSAGE_CONFLICT.
925
+ */
926
+ submitMessage: async (issueReportId, payload) => {
927
+ const res = await this.client.post(
928
+ `/api/v1/issue-reports/${issueReportId}/messages`,
929
+ payload,
930
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
931
+ );
932
+ return this.unwrapApiResponse(res, "Failed to submit issue report message");
933
+ },
934
+ /**
935
+ * Triage-operator message commands. These require a secret key context and a
936
+ * triage role/capability; publishable browser contexts are rejected by the
937
+ * server. Provide the secret key as the SDK's API key.
938
+ */
939
+ triage: {
940
+ /**
941
+ * List all projection rows for an app-scoped report (including corrected
942
+ * rows) for authorized triage operators.
943
+ */
944
+ listMessages: async (issueReportId) => {
945
+ const res = await this.client.get(
946
+ `/api/v1/issue-reports/triage/${issueReportId}/messages`,
947
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
948
+ );
949
+ return this.unwrapApiResponse(
950
+ res,
951
+ "Failed to list triage issue report messages"
952
+ );
953
+ },
954
+ /**
955
+ * Post a clarification_request or final_response reporter-visible message
956
+ * as a triage operator. Idempotent on `idempotency_key`; a key reused with
957
+ * a different body returns 409. A final_response can drive a terminal
958
+ * lifecycle transition via `resolve_case`; the internal resolution_note is
959
+ * never auto-displayed to reporters.
960
+ */
961
+ postMessage: async (issueReportId, payload) => {
962
+ const res = await this.client.post(
963
+ `/api/v1/issue-reports/triage/${issueReportId}/messages`,
964
+ payload,
965
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
966
+ );
967
+ return this.unwrapApiResponse(res, "Failed to post triage issue report message");
968
+ },
969
+ /**
970
+ * Retract an operator-authored message. The body is retained; the row's
971
+ * state is marked `retracted`. Retracting an already-retracted message is
972
+ * a safe no-op.
973
+ */
974
+ retractMessage: async (issueReportId, messageId, payload = {}) => {
975
+ const res = await this.client.post(
976
+ `/api/v1/issue-reports/triage/${issueReportId}/messages/${messageId}/retract`,
977
+ payload,
978
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
979
+ );
980
+ return this.unwrapApiResponse(res, "Failed to retract triage issue report message");
981
+ }
847
982
  }
848
983
  };
849
984
  /**
@@ -861,6 +996,17 @@ var SPAPSClient = class _SPAPSClient {
861
996
  );
862
997
  return this.unwrapApiResponse(res, "Failed to create app link");
863
998
  },
999
+ /**
1000
+ * Update a short link owned by the authenticated user.
1001
+ */
1002
+ update: async (username, slug, payload) => {
1003
+ const res = await this.client.patch(
1004
+ `/api/v1/app-links/${encodeURIComponent(username)}/${encodeURIComponent(slug)}`,
1005
+ payload,
1006
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
1007
+ );
1008
+ return this.unwrapApiResponse(res, "Failed to update app link");
1009
+ },
864
1010
  /**
865
1011
  * Resolve a public short link for the active application.
866
1012
  */
@@ -875,6 +1021,58 @@ var SPAPSClient = class _SPAPSClient {
875
1021
  return this.unwrapApiResponse(res, "Failed to get app link");
876
1022
  }
877
1023
  };
1024
+ /**
1025
+ * Marketing events and experiment results.
1026
+ *
1027
+ * Use a publishable key for browser-side event emission. Use a secret key
1028
+ * from a trusted server or agent process when reading experiment results.
1029
+ */
1030
+ marketing = {
1031
+ /**
1032
+ * Emit one anonymous attribution touch or experiment exposure.
1033
+ */
1034
+ emit: async (payload) => {
1035
+ if (payload.event_type === "experiment_exposure") {
1036
+ if (!payload.experiment_id?.trim()) {
1037
+ throw new Error("experiment_id is required for experiment_exposure events");
1038
+ }
1039
+ const variant = payload.variant_id ?? payload.variant;
1040
+ if (!variant?.trim()) {
1041
+ throw new Error("variant_id or variant is required for experiment_exposure events");
1042
+ }
1043
+ }
1044
+ const res = await this.client.post("/api/marketing-events/ingest", payload);
1045
+ return this.unwrapApiResponse(
1046
+ res,
1047
+ "Failed to emit marketing event"
1048
+ );
1049
+ },
1050
+ /**
1051
+ * Alias for emit(), matching the backend ingest endpoint language.
1052
+ */
1053
+ ingest: async (payload) => {
1054
+ return this.marketing.emit(payload);
1055
+ },
1056
+ /**
1057
+ * Read per-variant experiment results and the conservative stop signal.
1058
+ */
1059
+ getExperimentResults: async (experimentId) => {
1060
+ const encodedExperimentId = encodeURIComponent(experimentId);
1061
+ const res = await this.client.get(
1062
+ `/api/marketing/experiments/${encodedExperimentId}/results`
1063
+ );
1064
+ return this.unwrapApiResponse(
1065
+ res,
1066
+ "Failed to get marketing experiment results"
1067
+ );
1068
+ },
1069
+ /**
1070
+ * Short alias for getExperimentResults().
1071
+ */
1072
+ getResults: async (experimentId) => {
1073
+ return this.marketing.getExperimentResults(experimentId);
1074
+ }
1075
+ };
878
1076
  static envVar(name) {
879
1077
  if (typeof process !== "undefined" && process.env) {
880
1078
  return process.env[name];
@@ -1763,6 +1961,12 @@ var SPAPSClient = class _SPAPSClient {
1763
1961
  async listUsageHistory(query = {}) {
1764
1962
  return this.usage.listHistory(query);
1765
1963
  }
1964
+ async emitMarketingEvent(payload) {
1965
+ return this.marketing.emit(payload);
1966
+ }
1967
+ async getMarketingExperimentResults(experimentId) {
1968
+ return this.marketing.getExperimentResults(experimentId);
1969
+ }
1766
1970
  // Secure Messaging Methods
1767
1971
  async createSecureMessage(payload) {
1768
1972
  const response = await this.client.post("/api/secure-messages", payload);
package/dist/index.mjs CHANGED
@@ -528,6 +528,17 @@ var SPAPSClient = class _SPAPSClient {
528
528
  }
529
529
  return { headers: { "If-Match": String(ifMatch) } };
530
530
  }
531
+ requireCurrentUserIdFromAccessToken() {
532
+ if (!this.accessToken) {
533
+ throw new Error("Authentication required. Please authenticate first.");
534
+ }
535
+ const payload = TokenManager.decodePayload(this.accessToken);
536
+ const userId = payload?.user_id ?? payload?.sub;
537
+ if (typeof userId !== "string" || userId.length === 0) {
538
+ throw new Error("Current user id not found in access token.");
539
+ }
540
+ return userId;
541
+ }
531
542
  isAxiosResponse(value) {
532
543
  if (!value || typeof value !== "object") {
533
544
  return false;
@@ -637,7 +648,7 @@ var SPAPSClient = class _SPAPSClient {
637
648
  * @param userId - Optional user ID override (secret key contexts only).
638
649
  */
639
650
  check: async (key, userId) => {
640
- const q = new URLSearchParams({ key });
651
+ const q = new URLSearchParams({ entitlement_key: key });
641
652
  if (userId) q.append("user_id", userId);
642
653
  const res = await this.client.get(
643
654
  `/api/entitlements/check?${q.toString()}`,
@@ -651,7 +662,7 @@ var SPAPSClient = class _SPAPSClient {
651
662
  * Note: For publishable key contexts, only `resource_type=user` is permitted.
652
663
  * Non-user resource types will return a 403 error (enforced server-side).
653
664
  *
654
- * @param resourceType - The resource type (e.g. "user", "company", "org", "system").
665
+ * @param resourceType - The resource type (e.g. "user", "company", "org", "system", "project").
655
666
  * @param resourceId - Optional specific resource ID.
656
667
  */
657
668
  listByResource: async (resourceType, resourceId) => {
@@ -666,6 +677,46 @@ var SPAPSClient = class _SPAPSClient {
666
677
  return payload.entitlements;
667
678
  }
668
679
  return payload;
680
+ },
681
+ /**
682
+ * List project grants for the authenticated user.
683
+ *
684
+ * Browser/publishable-key contexts are safe here because the server requires
685
+ * the JWT subject to match the path user id.
686
+ */
687
+ listCurrentUserProjects: async (params) => {
688
+ const userId = this.requireCurrentUserIdFromAccessToken();
689
+ const q = new URLSearchParams();
690
+ if (params?.entitlementKey) q.append("entitlement_key", params.entitlementKey);
691
+ if (params?.limit !== void 0) q.append("limit", String(params.limit));
692
+ if (params?.offset !== void 0) q.append("offset", String(params.offset));
693
+ if (params?.cursor) q.append("cursor", params.cursor);
694
+ const qs = q.toString();
695
+ const res = await this.client.get(
696
+ `/api/project-grants/user/${encodeURIComponent(userId)}/projects${qs ? `?${qs}` : ""}`,
697
+ { headers: { Authorization: `Bearer ${this.accessToken}` } }
698
+ );
699
+ return this.unwrapApiResponse(res, "Failed to list current user project grants");
700
+ },
701
+ /**
702
+ * Check whether the authenticated user has one project entitlement.
703
+ *
704
+ * The helper does not accept user/email overrides; publishable-key callers
705
+ * are scoped by the server to the JWT identity.
706
+ */
707
+ checkCurrentUserProjectAccess: async (params) => {
708
+ if (!this.accessToken) {
709
+ throw new Error("Authentication required. Please authenticate first.");
710
+ }
711
+ const q = new URLSearchParams({
712
+ project_id: params.projectId,
713
+ entitlement_key: params.entitlementKey
714
+ });
715
+ const res = await this.client.get(
716
+ `/api/project-grants/check?${q.toString()}`,
717
+ { headers: { Authorization: `Bearer ${this.accessToken}` } }
718
+ );
719
+ return this.unwrapApiResponse(res, "Failed to check current user project access");
669
720
  }
670
721
  };
671
722
  /**
@@ -758,7 +809,7 @@ var SPAPSClient = class _SPAPSClient {
758
809
  /**
759
810
  * Return a short-lived private attachment access URL.
760
811
  */
761
- getAttachmentAccessUrl: async (attachmentId) => {
812
+ getAttachmentAccess: async (attachmentId) => {
762
813
  const res = await this.client.get(
763
814
  `/api/v1/issue-reports/attachments/${encodeURIComponent(attachmentId)}/access`,
764
815
  this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
@@ -768,6 +819,14 @@ var SPAPSClient = class _SPAPSClient {
768
819
  "Failed to get issue report attachment access URL"
769
820
  );
770
821
  },
822
+ /**
823
+ * Return a short-lived private attachment access URL.
824
+ *
825
+ * @deprecated Use getAttachmentAccess(attachmentId).
826
+ */
827
+ getAttachmentAccessUrl: async (attachmentId) => {
828
+ return this.issueReporting.getAttachmentAccess(attachmentId);
829
+ },
771
830
  /**
772
831
  * Soft-delete one owned screenshot attachment.
773
832
  */
@@ -809,6 +868,82 @@ var SPAPSClient = class _SPAPSClient {
809
868
  this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
810
869
  );
811
870
  return this.unwrapApiResponse(res, "Failed to reply to issue report");
871
+ },
872
+ /**
873
+ * List the chronological, reporter-visible message thread for one owned
874
+ * issue report. Only active, reporter-visible rows are returned; retracted
875
+ * or superseded operator messages are excluded and raw audit payloads are
876
+ * never exposed. `needs_response` badges an open clarification request.
877
+ */
878
+ listMessages: async (issueReportId) => {
879
+ const res = await this.client.get(
880
+ `/api/v1/issue-reports/${issueReportId}/messages`,
881
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
882
+ );
883
+ return this.unwrapApiResponse(res, "Failed to list issue report messages");
884
+ },
885
+ /**
886
+ * Submit a reporter clarification response on one owned issue report. This
887
+ * does NOT reopen a closed case (use `reply` for that). Idempotent on
888
+ * `idempotency_key`: same key + same body returns the existing message,
889
+ * same key + different body returns 409 ISSUE_REPORT_MESSAGE_CONFLICT.
890
+ */
891
+ submitMessage: async (issueReportId, payload) => {
892
+ const res = await this.client.post(
893
+ `/api/v1/issue-reports/${issueReportId}/messages`,
894
+ payload,
895
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
896
+ );
897
+ return this.unwrapApiResponse(res, "Failed to submit issue report message");
898
+ },
899
+ /**
900
+ * Triage-operator message commands. These require a secret key context and a
901
+ * triage role/capability; publishable browser contexts are rejected by the
902
+ * server. Provide the secret key as the SDK's API key.
903
+ */
904
+ triage: {
905
+ /**
906
+ * List all projection rows for an app-scoped report (including corrected
907
+ * rows) for authorized triage operators.
908
+ */
909
+ listMessages: async (issueReportId) => {
910
+ const res = await this.client.get(
911
+ `/api/v1/issue-reports/triage/${issueReportId}/messages`,
912
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
913
+ );
914
+ return this.unwrapApiResponse(
915
+ res,
916
+ "Failed to list triage issue report messages"
917
+ );
918
+ },
919
+ /**
920
+ * Post a clarification_request or final_response reporter-visible message
921
+ * as a triage operator. Idempotent on `idempotency_key`; a key reused with
922
+ * a different body returns 409. A final_response can drive a terminal
923
+ * lifecycle transition via `resolve_case`; the internal resolution_note is
924
+ * never auto-displayed to reporters.
925
+ */
926
+ postMessage: async (issueReportId, payload) => {
927
+ const res = await this.client.post(
928
+ `/api/v1/issue-reports/triage/${issueReportId}/messages`,
929
+ payload,
930
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
931
+ );
932
+ return this.unwrapApiResponse(res, "Failed to post triage issue report message");
933
+ },
934
+ /**
935
+ * Retract an operator-authored message. The body is retained; the row's
936
+ * state is marked `retracted`. Retracting an already-retracted message is
937
+ * a safe no-op.
938
+ */
939
+ retractMessage: async (issueReportId, messageId, payload = {}) => {
940
+ const res = await this.client.post(
941
+ `/api/v1/issue-reports/triage/${issueReportId}/messages/${messageId}/retract`,
942
+ payload,
943
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
944
+ );
945
+ return this.unwrapApiResponse(res, "Failed to retract triage issue report message");
946
+ }
812
947
  }
813
948
  };
814
949
  /**
@@ -826,6 +961,17 @@ var SPAPSClient = class _SPAPSClient {
826
961
  );
827
962
  return this.unwrapApiResponse(res, "Failed to create app link");
828
963
  },
964
+ /**
965
+ * Update a short link owned by the authenticated user.
966
+ */
967
+ update: async (username, slug, payload) => {
968
+ const res = await this.client.patch(
969
+ `/api/v1/app-links/${encodeURIComponent(username)}/${encodeURIComponent(slug)}`,
970
+ payload,
971
+ this.accessToken ? { headers: { Authorization: `Bearer ${this.accessToken}` } } : void 0
972
+ );
973
+ return this.unwrapApiResponse(res, "Failed to update app link");
974
+ },
829
975
  /**
830
976
  * Resolve a public short link for the active application.
831
977
  */
@@ -840,6 +986,58 @@ var SPAPSClient = class _SPAPSClient {
840
986
  return this.unwrapApiResponse(res, "Failed to get app link");
841
987
  }
842
988
  };
989
+ /**
990
+ * Marketing events and experiment results.
991
+ *
992
+ * Use a publishable key for browser-side event emission. Use a secret key
993
+ * from a trusted server or agent process when reading experiment results.
994
+ */
995
+ marketing = {
996
+ /**
997
+ * Emit one anonymous attribution touch or experiment exposure.
998
+ */
999
+ emit: async (payload) => {
1000
+ if (payload.event_type === "experiment_exposure") {
1001
+ if (!payload.experiment_id?.trim()) {
1002
+ throw new Error("experiment_id is required for experiment_exposure events");
1003
+ }
1004
+ const variant = payload.variant_id ?? payload.variant;
1005
+ if (!variant?.trim()) {
1006
+ throw new Error("variant_id or variant is required for experiment_exposure events");
1007
+ }
1008
+ }
1009
+ const res = await this.client.post("/api/marketing-events/ingest", payload);
1010
+ return this.unwrapApiResponse(
1011
+ res,
1012
+ "Failed to emit marketing event"
1013
+ );
1014
+ },
1015
+ /**
1016
+ * Alias for emit(), matching the backend ingest endpoint language.
1017
+ */
1018
+ ingest: async (payload) => {
1019
+ return this.marketing.emit(payload);
1020
+ },
1021
+ /**
1022
+ * Read per-variant experiment results and the conservative stop signal.
1023
+ */
1024
+ getExperimentResults: async (experimentId) => {
1025
+ const encodedExperimentId = encodeURIComponent(experimentId);
1026
+ const res = await this.client.get(
1027
+ `/api/marketing/experiments/${encodedExperimentId}/results`
1028
+ );
1029
+ return this.unwrapApiResponse(
1030
+ res,
1031
+ "Failed to get marketing experiment results"
1032
+ );
1033
+ },
1034
+ /**
1035
+ * Short alias for getExperimentResults().
1036
+ */
1037
+ getResults: async (experimentId) => {
1038
+ return this.marketing.getExperimentResults(experimentId);
1039
+ }
1040
+ };
843
1041
  static envVar(name) {
844
1042
  if (typeof process !== "undefined" && process.env) {
845
1043
  return process.env[name];
@@ -1728,6 +1926,12 @@ var SPAPSClient = class _SPAPSClient {
1728
1926
  async listUsageHistory(query = {}) {
1729
1927
  return this.usage.listHistory(query);
1730
1928
  }
1929
+ async emitMarketingEvent(payload) {
1930
+ return this.marketing.emit(payload);
1931
+ }
1932
+ async getMarketingExperimentResults(experimentId) {
1933
+ return this.marketing.getExperimentResults(experimentId);
1934
+ }
1731
1935
  // Secure Messaging Methods
1732
1936
  async createSecureMessage(payload) {
1733
1937
  const response = await this.client.post("/api/secure-messages", payload);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spaps-sdk",
3
- "version": "1.10.1",
3
+ "version": "1.11.0",
4
4
  "description": "Sweet Potato Authentication & Payment Service SDK - Zero-config client with built-in permission checking, role-based access control, and dayrate scheduling",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -51,7 +51,7 @@
51
51
  "dependencies": {
52
52
  "axios": "^1.15.1",
53
53
  "cross-fetch": "^4.0.0",
54
- "spaps-types": "^1.4.0"
54
+ "spaps-types": "^1.4.2"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^20.10.0",