@itm-studio/partner-sdk 0.2.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -2
- package/dist/index.d.mts +339 -2
- package/dist/index.d.ts +339 -2
- package/dist/index.js +1050 -609
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1050 -610
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/schema/partner.graphql +198 -0
package/README.md
CHANGED
|
@@ -103,6 +103,71 @@ export function createITMClientForRequest(request: Request) {
|
|
|
103
103
|
|
|
104
104
|
If you also send `input.compliance`, the backend keeps the values you provide and backfills missing `ipAddress` and `userAgent` from the forwarded headers when available.
|
|
105
105
|
|
|
106
|
+
### Brand Agent session authentication (first-party only)
|
|
107
|
+
|
|
108
|
+
First-party ITM agents authenticate with a short-lived Brand Agent session
|
|
109
|
+
instead of a partner API token. This mode is not available to external
|
|
110
|
+
integrators: it requires workload identity (e.g. a Vercel OIDC token) trusted
|
|
111
|
+
by the ITM backend and a backend-registered agent session, and only the
|
|
112
|
+
capability-scoped operations granted to the session (`CREATE_MOMENT`,
|
|
113
|
+
`SEND_EMAIL`, `READ_MOMENT_ANALYTICS`) will authorize. Everything else stays
|
|
114
|
+
partner-token-only, and a request carrying both a partner API key and an
|
|
115
|
+
agent session is rejected as ambiguous.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { createITMPartnerClient } from "@itm-studio/partner-sdk";
|
|
119
|
+
|
|
120
|
+
const itm = createITMPartnerClient({
|
|
121
|
+
agentSession: {
|
|
122
|
+
// Minted fresh for every request; never cached or persisted.
|
|
123
|
+
workloadToken: () => getWorkloadOidcToken(),
|
|
124
|
+
sessionUid,
|
|
125
|
+
bootstrapToken, // optional one-time session bootstrap
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The brand is always derived server-side from the session — this mode never
|
|
131
|
+
carries a brand identifier or a partner API key. Agent-session mutations must
|
|
132
|
+
supply `toolCallId` on their inputs, plus `idempotencyKey` where it is not
|
|
133
|
+
already required (`CreatePartnerMomentInput`; `SendPartnerAgentEmailInput`
|
|
134
|
+
requires `idempotencyKey` for every caller). Partner-token callers must omit
|
|
135
|
+
the agent-only fields (`toolCallId`, and `idempotencyKey` on
|
|
136
|
+
`CreatePartnerMomentInput`).
|
|
137
|
+
|
|
138
|
+
### Brand Agent access-token authentication (first-party only)
|
|
139
|
+
|
|
140
|
+
A third mode exists for the one moment in the session lifecycle when no agent
|
|
141
|
+
session exists yet: fetching a conversation thread's seed context while a new
|
|
142
|
+
session is being created. It authenticates with workload identity plus the
|
|
143
|
+
short-lived agent access token the caller presented, and it authorizes only
|
|
144
|
+
the operations guarded for it (currently `getAgentThreadSeed`, which takes no
|
|
145
|
+
arguments — the admin, brand, and thread scope all come from the token's
|
|
146
|
+
verified claims).
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const itm = createITMPartnerClient({
|
|
150
|
+
agentAccess: {
|
|
151
|
+
// Minted fresh for every request; never cached or persisted.
|
|
152
|
+
workloadToken: () => getWorkloadOidcToken(),
|
|
153
|
+
// The raw bearer from the inbound session-create request.
|
|
154
|
+
accessToken,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const { getAgentThreadSeed } = await itm.query({
|
|
159
|
+
getAgentThreadSeed: {
|
|
160
|
+
seed: true,
|
|
161
|
+
includedMessages: true,
|
|
162
|
+
totalMessages: true,
|
|
163
|
+
truncated: true,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Exactly one of `token`, `agentSession`, or `agentAccess` must be provided;
|
|
169
|
+
combining modes is rejected.
|
|
170
|
+
|
|
106
171
|
## Money Amounts
|
|
107
172
|
|
|
108
173
|
Dynamic-pricing price values use the currency's integer minor unit: cents for
|
|
@@ -236,6 +301,52 @@ basis points (`6000` = 60%). Refresh after `validUntil`. Because price is stored
|
|
|
236
301
|
per tier, every upcoming admission window for a managed tier must fall within
|
|
237
302
|
the configured horizon before that tier is eligible.
|
|
238
303
|
|
|
304
|
+
## AgentMail
|
|
305
|
+
|
|
306
|
+
Queue one to ten personalized plain-text emails from the authenticated brand's
|
|
307
|
+
dedicated AgentMail inbox. Each item is a separate single-recipient message.
|
|
308
|
+
The mutation returns after the durable batch is accepted; query the batch for
|
|
309
|
+
final per-message provider status.
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
const queued = await client.mutation({
|
|
313
|
+
sendPartnerAgentEmail: {
|
|
314
|
+
__args: {
|
|
315
|
+
input: {
|
|
316
|
+
idempotencyKey: "venue-outreach-2026-08-13",
|
|
317
|
+
messages: [
|
|
318
|
+
{
|
|
319
|
+
to: "bookings@venue.example",
|
|
320
|
+
subject: "October show inquiry",
|
|
321
|
+
text: "Would October 10 work for a 200-person show?",
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
uid: true,
|
|
327
|
+
status: true,
|
|
328
|
+
acceptedCount: true,
|
|
329
|
+
messages: { uid: true, recipient: true, status: true },
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const status = await client.query({
|
|
334
|
+
getPartnerAgentEmailBatch: {
|
|
335
|
+
__args: { batchUid: queued.sendPartnerAgentEmail.uid },
|
|
336
|
+
status: true,
|
|
337
|
+
messages: {
|
|
338
|
+
recipient: true,
|
|
339
|
+
status: true,
|
|
340
|
+
agentMailMessageId: true,
|
|
341
|
+
lastErrorCode: true,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Reuse an idempotency key only for the exact same logical batch. `SENT` means
|
|
348
|
+
AgentMail accepted the message; it does not prove inbox delivery.
|
|
349
|
+
|
|
239
350
|
## Available Queries
|
|
240
351
|
|
|
241
352
|
### `getPartnerBrand`
|
|
@@ -277,7 +388,10 @@ const { getPartnerMomentsForBrand } = await itm.query({
|
|
|
277
388
|
take: 20, // items per page (default: 50, omit to use default)
|
|
278
389
|
cursor: nextCursor, // from previous response
|
|
279
390
|
status: "UPCOMING", // UPCOMING | LIVE | ENDED
|
|
280
|
-
filters: {
|
|
391
|
+
filters: {
|
|
392
|
+
search: "summer launch", // case-insensitive name or slug match
|
|
393
|
+
includePrivate: true, // include private moments owned by your brand
|
|
394
|
+
},
|
|
281
395
|
sortOrder: "ASC", // ASC | DESC by start date (default: DESC)
|
|
282
396
|
},
|
|
283
397
|
moments: { uid: true, name: true, startDate: true },
|
|
@@ -292,6 +406,9 @@ By default, only public moments are returned. Set
|
|
|
292
406
|
`filters.includePrivate` to `true` to return both public and private moments
|
|
293
407
|
owned by the brand associated with the partner token. Omitting `filters`, or
|
|
294
408
|
setting `includePrivate` to `false`, preserves the public-only behavior.
|
|
409
|
+
`filters.search` matches moment names and slugs case-insensitively, is limited
|
|
410
|
+
to 100 characters, and composes with visibility, status, sorting, and cursor
|
|
411
|
+
pagination.
|
|
295
412
|
|
|
296
413
|
### `getPartnerDrops`
|
|
297
414
|
|
|
@@ -447,9 +564,13 @@ const { getMomentLineupByToken } = await itm.query({
|
|
|
447
564
|
|
|
448
565
|
### `getPartnerMomentStats`
|
|
449
566
|
|
|
450
|
-
Get cached partner-facing counts for a moment owned by the authenticated partner's brand.
|
|
567
|
+
Get cached partner-facing counts for a moment owned by the authenticated partner's brand. Counts
|
|
568
|
+
can be up to five minutes old.
|
|
451
569
|
|
|
452
570
|
- `ticketsCount` counts confirmed tickets only.
|
|
571
|
+
- `generatedAt` identifies when this cached count set was computed.
|
|
572
|
+
- `paidTicketsCount` counts confirmed tickets with a materialized price above zero.
|
|
573
|
+
- `freeTicketsCount` counts confirmed tickets with a zero materialized price.
|
|
453
574
|
- `waitlistCount` counts active waitlist entries.
|
|
454
575
|
- `externalRsvpCount` counts external RSVPs.
|
|
455
576
|
- `ticketRsvpRequestsCount` counts ticket RSVP requests.
|
|
@@ -458,7 +579,10 @@ Get cached partner-facing counts for a moment owned by the authenticated partner
|
|
|
458
579
|
const { getPartnerMomentStats } = await itm.query({
|
|
459
580
|
getPartnerMomentStats: {
|
|
460
581
|
__args: { momentSlug: "my-event-slug" },
|
|
582
|
+
generatedAt: true,
|
|
461
583
|
ticketsCount: true,
|
|
584
|
+
paidTicketsCount: true,
|
|
585
|
+
freeTicketsCount: true,
|
|
462
586
|
waitlistCount: true,
|
|
463
587
|
externalRsvpCount: true,
|
|
464
588
|
ticketRsvpRequestsCount: true,
|
|
@@ -617,6 +741,55 @@ const { getPartnerCustomersForMoment } = await itm.query({
|
|
|
617
741
|
});
|
|
618
742
|
```
|
|
619
743
|
|
|
744
|
+
### `getPartnerAudience`
|
|
745
|
+
|
|
746
|
+
Get the authenticated brand's customer audience with explicit filters, one
|
|
747
|
+
deterministic sort, and opaque cursor pagination. Call this only from a trusted
|
|
748
|
+
Node.js server: the partner token and returned customer data must never be
|
|
749
|
+
embedded in a browser/mobile bundle or written to application logs.
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
const { getPartnerAudience } = await itm.query({
|
|
753
|
+
getPartnerAudience: {
|
|
754
|
+
__args: {
|
|
755
|
+
input: {
|
|
756
|
+
filters: {
|
|
757
|
+
search: "john@example.com",
|
|
758
|
+
emailConsentStatuses: ["SUBSCRIBED"],
|
|
759
|
+
momentUids: ["moment-uid"],
|
|
760
|
+
minTotalSpendUsdCents: 5000,
|
|
761
|
+
},
|
|
762
|
+
sort: { field: "TOTAL_SPEND_USD", direction: "DESC" },
|
|
763
|
+
take: 50, // maximum 100
|
|
764
|
+
includeTotalCount: true, // omitted by default to avoid the count query
|
|
765
|
+
},
|
|
766
|
+
},
|
|
767
|
+
customers: {
|
|
768
|
+
uid: true, // brand-scoped customer UID
|
|
769
|
+
name: true,
|
|
770
|
+
email: true,
|
|
771
|
+
phoneNumber: true,
|
|
772
|
+
emailConsentStatus: true,
|
|
773
|
+
smsConsentStatus: true,
|
|
774
|
+
communicationsOptedOut: true,
|
|
775
|
+
isSuppressed: true,
|
|
776
|
+
totalSpendUsdCents: true,
|
|
777
|
+
numMoments: true,
|
|
778
|
+
updatedAt: true,
|
|
779
|
+
},
|
|
780
|
+
totalCount: true,
|
|
781
|
+
hasNextPage: true,
|
|
782
|
+
nextCursor: true,
|
|
783
|
+
},
|
|
784
|
+
});
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
Filters are combined with AND; values inside one list are combined with OR.
|
|
788
|
+
`from` timestamps are inclusive and `before` timestamps are exclusive. Audience
|
|
789
|
+
pages and optional exact counts can be up to 30 seconds old. Consent and
|
|
790
|
+
suppression fields reflect ITM's current brand-scoped records but do not replace
|
|
791
|
+
your legal-purpose and communications-compliance checks.
|
|
792
|
+
|
|
620
793
|
### `getPartnerMomentCollection`
|
|
621
794
|
|
|
622
795
|
Get a moment collection by slug. Collections group related moments together (e.g. a summer series, a festival lineup). Returns the collection with its moments and links.
|