@alvera-ai/platform-sdk 0.10.0-rc.8 → 0.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.
Files changed (66) hide show
  1. package/.agent/AGENTS.md +499 -0
  2. package/.agent/account_management.md +456 -0
  3. package/.agent/action_status_updaters.md +264 -0
  4. package/.agent/ai_agents.md +462 -0
  5. package/.agent/ai_sandbox.md +265 -0
  6. package/.agent/async.md +112 -0
  7. package/.agent/connected_apps.md +408 -0
  8. package/.agent/cookbook/_fixtures/README.md +106 -0
  9. package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_customer.liquid +32 -0
  10. package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_mdm.liquid +20 -0
  11. package/.agent/cookbook/_fixtures/accounts_receivable/stripe_customers_batch1.csv +5 -0
  12. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_generic_table.liquid +33 -0
  13. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_legal_entity.liquid +88 -0
  14. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_mdm.liquid +48 -0
  15. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid +47 -0
  16. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_mdm.liquid +24 -0
  17. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_patient.liquid +38 -0
  18. package/.agent/cookbook/_fixtures/healthcare/memorandum-of-association-01.png +0 -0
  19. package/.agent/cookbook/_fixtures/healthcare/sample_two_page.pdf +43 -0
  20. package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_compliance_screening.liquid +59 -0
  21. package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_mdm.liquid +36 -0
  22. package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_mdm.liquid +30 -0
  23. package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_payment_account.liquid +55 -0
  24. package/.agent/cookbook/_setup/accounts_receivable.md +282 -0
  25. package/.agent/cookbook/_setup/foundation.md +277 -0
  26. package/.agent/cookbook/_setup/healthcare.md +279 -0
  27. package/.agent/cookbook/_setup/payment_risk.md +283 -0
  28. package/.agent/cookbook/action-status-updaters.md +212 -0
  29. package/.agent/cookbook/ai-agent-invoke.md +243 -0
  30. package/.agent/cookbook/appointment-review-sms-workflow.md +761 -0
  31. package/.agent/cookbook/birthday-greeting-sms-trigger.md +656 -0
  32. package/.agent/cookbook/bulk-ingest.md +254 -0
  33. package/.agent/cookbook/contact-us-triage-with-llm.md +622 -0
  34. package/.agent/cookbook/custom-tables.md +201 -0
  35. package/.agent/cookbook/dunning-sms-for-delinquent.md +619 -0
  36. package/.agent/cookbook/invite-team.md +194 -0
  37. package/.agent/cookbook/kyc-notification-on-account-activation.md +619 -0
  38. package/.agent/cookbook/rest-fetch.md +246 -0
  39. package/.agent/cookbook/sanctions-screening-with-agent-review.md +733 -0
  40. package/.agent/cookbook/score-leads-with-llm-categorization.md +624 -0
  41. package/.agent/cookbook/system-templates.md +129 -0
  42. package/.agent/cookbook/triage-prospects-by-priority.md +533 -0
  43. package/.agent/cookbook/welcome-sms-for-customers.md +607 -0
  44. package/.agent/data_activation_clients.md +559 -0
  45. package/.agent/data_sources.md +235 -0
  46. package/.agent/datalakes.md +714 -0
  47. package/.agent/debugging.md +137 -0
  48. package/.agent/errors.md +190 -0
  49. package/.agent/generic_tables.md +351 -0
  50. package/.agent/interoperability_contracts.md +417 -0
  51. package/.agent/mdm.md +293 -0
  52. package/.agent/mutations.md +126 -0
  53. package/.agent/templates.md +98 -0
  54. package/.agent/tool-call-configs.md +90 -0
  55. package/.agent/tools.md +547 -0
  56. package/.agent/type_naming.md +129 -0
  57. package/.agent/workflows.md +617 -0
  58. package/README.md +178 -0
  59. package/dist/bin/platform-sdk.d.mts +1 -0
  60. package/dist/bin/platform-sdk.mjs +106 -0
  61. package/dist/bin/platform-sdk.mjs.map +1 -0
  62. package/dist/index.d.mts +1310 -44116
  63. package/dist/index.d.mts.map +1 -1
  64. package/dist/index.mjs +1194 -7344
  65. package/dist/index.mjs.map +1 -1
  66. package/package.json +19 -10
@@ -0,0 +1,194 @@
1
+ ---
2
+ title: "Capability: invite a teammate into your tenant"
3
+ summary: A capability walk for multi-user onboarding. An admin invites a colleague by email (`api.invitations.create`), the colleague signs up and is confirmed, signs in without a tenant to find the pending invite (`invitations.list`), accepts it (`invitations.accept`), and finally signs in tenant-scoped as a member. Shows the three session scopes — root, tenantless, tenant-scoped — in one flow.
4
+ industry: accounts_receivable
5
+ slug: invite-team
6
+ vitest_source:
7
+ - integration-tests/tests/accounts_receivable/invite-team.test.ts
8
+ - integration-tests/tests/accounts_receivable/bootstrap.test.ts
9
+ status: green
10
+ ---
11
+
12
+ # Capability
13
+
14
+ **What you get:** a second person on your tenant, added entirely through the
15
+ SDK — no console, no out-of-band steps.
16
+
17
+ The flow touches all three session scopes (see `AGENTS.md` § SDK auth):
18
+
19
+ - **tenant-scoped** (the admin) — `api.invitations.create(tenantSlug, { email, role })`
20
+ - **root** — signs the new user up and confirms them
21
+ (`api.auth.signUp` + `api.admin.confirmUser`)
22
+ - **tenantless** (the invitee, before they belong anywhere) — lists pending
23
+ invites and accepts one (`api.invitations.list` / `api.invitations.accept`)
24
+
25
+ After accepting, the invitee re-authenticates **tenant-scoped** and is a member.
26
+ This capability is **global** — it works the same on every datalake's tenant.
27
+
28
+ # Walkthrough
29
+
30
+ The `_setup/accounts_receivable.md` bootstrap already provisioned the tenant and
31
+ left `api` as the admin's tenant-scoped client, with `tenantSlug` and
32
+ `ctx.sarahEmail` / `ctx.sarahPassword` populated. Root credentials come from the
33
+ `ALVERA_ROOT_EMAIL` / `ALVERA_ROOT_PASSWORD` env vars the validator already
34
+ exports.
35
+
36
+ ## 001 — confirm the admin can invite
37
+
38
+ Before inviting, verify the current session is a tenant-scoped admin of the
39
+ expected tenant. `api.sessions.verify()` echoes the session's `tenant.slug` and
40
+ `role.name`. Only an admin can create invitations.
41
+
42
+ ```typescript
43
+ const { data: who } = await api.sessions.verify()
44
+ if (who.tenant?.slug !== tenantSlug) {
45
+ throw new Error(`expected an admin session on ${tenantSlug}, got ${who.tenant?.slug}`)
46
+ }
47
+ if (!/admin/i.test(who.role?.name ?? '')) {
48
+ throw new Error(`expected an admin role, got ${who.role?.name}`)
49
+ }
50
+ ```
51
+
52
+ ## 002 — invite the teammate by email
53
+
54
+ The admin invites a colleague with their email and a role (`member`). The
55
+ invite is keyed on `(tenant, email)`, so re-inviting the same address returns a
56
+ 422 with "already invited" — handle that as success so the step is idempotent on
57
+ re-runs. The teammate's per-run email is derived from `runSuffix` so parallel
58
+ runs don't collide.
59
+
60
+ ```typescript
61
+ ctx.emmaEmail = `cookbook-emma-${runSuffix}@dev.local`
62
+ ctx.emmaPassword = 'CookbookPass1!'
63
+
64
+ try {
65
+ const { data: invite } = await api.invitations.create(tenantSlug, {
66
+ email: ctx.emmaEmail,
67
+ role: 'member',
68
+ })
69
+ if (invite.email !== ctx.emmaEmail || invite.role !== 'member') {
70
+ throw new Error(`unexpected invite: ${JSON.stringify(invite)}`)
71
+ }
72
+ } catch (err) {
73
+ const detail = JSON.stringify(err)
74
+ if (!detail.includes('already invited')) throw err
75
+ // already invited on a re-run — fine
76
+ }
77
+ ```
78
+
79
+ ## 003 — root signs the teammate up and confirms them
80
+
81
+ The invitee needs an account before they can accept. Signup + confirmation are
82
+ root-scoped actions, so build a root client from the `ALVERA_ROOT_*` credentials
83
+ and use it. `confirmUser` activates the account so the invitee can sign in.
84
+
85
+ ```typescript
86
+ const rootSession = await createSession({
87
+ baseUrl: process.env.ALVERA_BASE_URL!,
88
+ email: process.env.ALVERA_ROOT_EMAIL!,
89
+ password: process.env.ALVERA_ROOT_PASSWORD!,
90
+ })
91
+ const rootApi = createIsolatedPlatformApi({
92
+ baseUrl: process.env.ALVERA_BASE_URL!,
93
+ sessionToken: rootSession.sessionToken,
94
+ })
95
+
96
+ const { data: emmaUser } = await rootApi.auth.signUp({
97
+ email: ctx.emmaEmail,
98
+ password: ctx.emmaPassword,
99
+ first_name: 'Emma',
100
+ last_name: 'Wilson',
101
+ })
102
+ await rootApi.admin.confirmUser(emmaUser.id!)
103
+ ```
104
+
105
+ ## 004 — teammate signs in WITHOUT a tenant and finds the invite
106
+
107
+ The invitee has an account but belongs to no tenant yet, so they sign in
108
+ **tenantless** — `createSession` with no `tenantSlug`. The resulting session has
109
+ `tenant: null`. A tenantless client can list the invitations waiting for that
110
+ user. Find the one for this tenant by matching `tenant.slug`.
111
+
112
+ ```typescript
113
+ const emmaTenantless = await createSession({
114
+ baseUrl: process.env.ALVERA_BASE_URL!,
115
+ email: ctx.emmaEmail,
116
+ password: ctx.emmaPassword,
117
+ // no tenantSlug → tenantless session
118
+ })
119
+ if (emmaTenantless.tenant !== null) {
120
+ throw new Error('expected a tenantless session (tenant should be null)')
121
+ }
122
+ const emmaTenantlessApi = createIsolatedPlatformApi({
123
+ baseUrl: process.env.ALVERA_BASE_URL!,
124
+ sessionToken: emmaTenantless.sessionToken,
125
+ })
126
+ ctx.emmaTenantlessToken = emmaTenantless.sessionToken
127
+
128
+ const { data: invites } = await emmaTenantlessApi.invitations.list()
129
+ const ours = (invites.data ?? []).find((i) => i.tenant?.slug === tenantSlug)
130
+ if (!ours?.id) {
131
+ throw new Error(`no pending invite for ${tenantSlug} found for the new user`)
132
+ }
133
+ ctx.invitationId = ours.id
134
+ ```
135
+
136
+ ## 005 — teammate accepts the invitation
137
+
138
+ Accepting consumes the invite and creates the membership. The response echoes
139
+ the new `role` and the `tenant.slug` the user just joined.
140
+
141
+ ```typescript
142
+ const emmaTenantlessApi = createIsolatedPlatformApi({
143
+ baseUrl: process.env.ALVERA_BASE_URL!,
144
+ sessionToken: ctx.emmaTenantlessToken,
145
+ })
146
+
147
+ const { data: membership } = await emmaTenantlessApi.invitations.accept(ctx.invitationId)
148
+ if (membership.tenant?.slug !== tenantSlug || membership.role !== 'member') {
149
+ throw new Error(`unexpected membership: ${JSON.stringify(membership)}`)
150
+ }
151
+ ```
152
+
153
+ ## 006 — teammate signs in tenant-scoped as a member
154
+
155
+ Now that the membership exists, the invitee re-authenticates **with** the
156
+ tenant slug. The new session carries `tenant.slug` and a `member` role — they
157
+ are in.
158
+
159
+ ```typescript
160
+ const emmaScoped = await createSession({
161
+ baseUrl: process.env.ALVERA_BASE_URL!,
162
+ email: ctx.emmaEmail,
163
+ password: ctx.emmaPassword,
164
+ tenantSlug,
165
+ })
166
+ if (emmaScoped.tenant?.slug !== tenantSlug) {
167
+ throw new Error(`expected a tenant-scoped session on ${tenantSlug}`)
168
+ }
169
+ if (!/member/i.test(emmaScoped.role?.name ?? '')) {
170
+ throw new Error(`expected a member role, got ${emmaScoped.role?.name}`)
171
+ }
172
+ ```
173
+
174
+ # Gotchas
175
+
176
+ - **Invitations are created tenant-scoped, accepted tenantless.** The admin
177
+ calls `invitations.create(tenantSlug, …)` on their tenant-scoped client; the
178
+ invitee calls `invitations.list()` / `accept(id)` on a **tenantless** client
179
+ (they have no tenant yet). Mixing the scopes up is the #1 mistake here.
180
+ - **Re-inviting the same email 422s.** The `(tenant, email)` pair is unique —
181
+ catch "already invited" and treat it as success for idempotent re-runs.
182
+ - **The invitee re-authenticates twice.** Once tenantless (to find + accept the
183
+ invite), then again tenant-scoped (to act as a member). Accepting does not
184
+ upgrade the existing tenantless session in place — you mint a new one.
185
+ - **Signup + confirm are root-scoped.** A tenant admin can invite, but only root
186
+ can create and confirm the underlying user account.
187
+
188
+ # See also
189
+
190
+ - `AGENTS.md` § SDK auth + client construction — root / tenantless / tenant-scoped
191
+ - `account_management.md` — users, roles, invitations reference
192
+ - `_setup/accounts_receivable.md` — the bootstrap this walk starts from
193
+ - `integration-tests/tests/accounts_receivable/invite-team.test.ts` — the green
194
+ test these calls are lifted from