@alvera-ai/platform-sdk 0.10.0-rc.9 → 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.
- package/.agent/AGENTS.md +499 -0
- package/.agent/account_management.md +456 -0
- package/.agent/action_status_updaters.md +264 -0
- package/.agent/ai_agents.md +462 -0
- package/.agent/ai_sandbox.md +265 -0
- package/.agent/async.md +112 -0
- package/.agent/connected_apps.md +408 -0
- package/.agent/cookbook/_fixtures/README.md +106 -0
- package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_customer.liquid +32 -0
- package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_mdm.liquid +20 -0
- package/.agent/cookbook/_fixtures/accounts_receivable/stripe_customers_batch1.csv +5 -0
- package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_generic_table.liquid +33 -0
- package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_legal_entity.liquid +88 -0
- package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_mdm.liquid +48 -0
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid +47 -0
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_mdm.liquid +24 -0
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_patient.liquid +38 -0
- package/.agent/cookbook/_fixtures/healthcare/memorandum-of-association-01.png +0 -0
- package/.agent/cookbook/_fixtures/healthcare/sample_two_page.pdf +43 -0
- package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_compliance_screening.liquid +59 -0
- package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_mdm.liquid +36 -0
- package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_mdm.liquid +30 -0
- package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_payment_account.liquid +55 -0
- package/.agent/cookbook/_setup/accounts_receivable.md +282 -0
- package/.agent/cookbook/_setup/foundation.md +277 -0
- package/.agent/cookbook/_setup/healthcare.md +279 -0
- package/.agent/cookbook/_setup/payment_risk.md +283 -0
- package/.agent/cookbook/action-status-updaters.md +212 -0
- package/.agent/cookbook/ai-agent-invoke.md +243 -0
- package/.agent/cookbook/appointment-review-sms-workflow.md +761 -0
- package/.agent/cookbook/birthday-greeting-sms-trigger.md +656 -0
- package/.agent/cookbook/bulk-ingest.md +254 -0
- package/.agent/cookbook/contact-us-triage-with-llm.md +622 -0
- package/.agent/cookbook/custom-tables.md +201 -0
- package/.agent/cookbook/dunning-sms-for-delinquent.md +619 -0
- package/.agent/cookbook/invite-team.md +194 -0
- package/.agent/cookbook/kyc-notification-on-account-activation.md +619 -0
- package/.agent/cookbook/rest-fetch.md +246 -0
- package/.agent/cookbook/sanctions-screening-with-agent-review.md +733 -0
- package/.agent/cookbook/score-leads-with-llm-categorization.md +624 -0
- package/.agent/cookbook/system-templates.md +129 -0
- package/.agent/cookbook/triage-prospects-by-priority.md +533 -0
- package/.agent/cookbook/welcome-sms-for-customers.md +607 -0
- package/.agent/data_activation_clients.md +559 -0
- package/.agent/data_sources.md +235 -0
- package/.agent/datalakes.md +714 -0
- package/.agent/debugging.md +137 -0
- package/.agent/errors.md +190 -0
- package/.agent/generic_tables.md +351 -0
- package/.agent/interoperability_contracts.md +417 -0
- package/.agent/mdm.md +293 -0
- package/.agent/mutations.md +126 -0
- package/.agent/templates.md +98 -0
- package/.agent/tool-call-configs.md +90 -0
- package/.agent/tools.md +547 -0
- package/.agent/type_naming.md +129 -0
- package/.agent/workflows.md +617 -0
- package/README.md +178 -0
- package/dist/bin/platform-sdk.d.mts +1 -0
- package/dist/bin/platform-sdk.mjs +106 -0
- package/dist/bin/platform-sdk.mjs.map +1 -0
- package/dist/index.d.mts +1310 -44116
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1194 -7344
- package/dist/index.mjs.map +1 -1
- package/package.json +18 -9
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
# Account management
|
|
2
|
+
|
|
3
|
+
Every SDK call resolves against a **trust boundary** with three
|
|
4
|
+
coordinates:
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
who User (human) OR ApiKey / PAT (agent)
|
|
8
|
+
where Tenant (+ optional Datalake)
|
|
9
|
+
what Role assumed by this session
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Account management is how those three coordinates are established
|
|
13
|
+
and reasoned about. This page covers six SDK namespaces that work
|
|
14
|
+
together:
|
|
15
|
+
|
|
16
|
+
| Namespace | Methods |
|
|
17
|
+
|--------------------|-----------------------------------------------|
|
|
18
|
+
| `api.auth` | `signUp` |
|
|
19
|
+
| `api.admin` | `confirmUser` — **dev fixture only** |
|
|
20
|
+
| `api.sessions` | `verify` |
|
|
21
|
+
| `api.tenants` | `create`, `list` |
|
|
22
|
+
| `api.invitations` | `create`, `list`, `accept` |
|
|
23
|
+
| `api.ping` | unauthenticated health check (`GET /api/ping`) |
|
|
24
|
+
| `createSession` | mint a Bearer (top-level helper, not a namespace) |
|
|
25
|
+
| `revokeSession` | revoke the current Bearer (top-level helper, parallel to `createSession`) |
|
|
26
|
+
|
|
27
|
+
Two top-level concerns sit above the surface:
|
|
28
|
+
|
|
29
|
+
1. **Who is calling** — a human (Bearer) or an agent (API key / PAT)
|
|
30
|
+
2. **What that caller can do** — gated by role + by credential type
|
|
31
|
+
|
|
32
|
+
Both branches differ in lifecycle, capability, and whether they
|
|
33
|
+
can be created programmatically. See §3 "Access control".
|
|
34
|
+
|
|
35
|
+
## 1. Identity — humans
|
|
36
|
+
|
|
37
|
+
Humans authenticate by exchanging email+password for a Bearer.
|
|
38
|
+
The Bearer carries the trust-boundary coordinates and travels in
|
|
39
|
+
the `Authorization: Bearer <token>` header on every subsequent
|
|
40
|
+
request.
|
|
41
|
+
|
|
42
|
+
### Three Bearer scopes
|
|
43
|
+
|
|
44
|
+
`createSession` mints a Bearer at one of three scopes:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { createSession } from '@alvera-ai/platform-sdk'
|
|
48
|
+
|
|
49
|
+
// root admin (no tenant) — for confirmUser, signUp on behalf of, etc.
|
|
50
|
+
const root = await createSession({
|
|
51
|
+
baseUrl, email: rootEmail, password: rootPassword,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// tenantless — authenticated user who has not picked a tenant yet
|
|
55
|
+
const tenantless = await createSession({
|
|
56
|
+
baseUrl, email, password,
|
|
57
|
+
})
|
|
58
|
+
// tenantless.tenant === null
|
|
59
|
+
|
|
60
|
+
// tenant-scoped — the canonical Bearer for tenant operations
|
|
61
|
+
const tenant = await createSession({
|
|
62
|
+
baseUrl, email, password, tenantSlug,
|
|
63
|
+
})
|
|
64
|
+
// tenant.tenant.slug === tenantSlug
|
|
65
|
+
// tenant.role.name === 'tenant_admin' | 'member' | ...
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
See `AGENTS.md` "Sessions come in three scopes" for when each is
|
|
69
|
+
appropriate. Tenantless is a transient state used once: to create
|
|
70
|
+
a tenant (creator path) or accept an invitation (invitee path).
|
|
71
|
+
|
|
72
|
+
### Explicit teardown — `revokeSession`
|
|
73
|
+
|
|
74
|
+
The inverse of `createSession`. Revokes the current Bearer on
|
|
75
|
+
the server and short-circuits the local `api` instance — every
|
|
76
|
+
subsequent request through the same instance returns 401:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { revokeSession } from '@alvera-ai/platform-sdk'
|
|
80
|
+
|
|
81
|
+
await revokeSession() // DELETE /api/v1/sessions
|
|
82
|
+
// api.* calls now return 401
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Use this for graceful sign-out flows in long-running consumers
|
|
86
|
+
(CLIs, daemons). Letting the Bearer reach `expires_at` is fine
|
|
87
|
+
for short-lived scripts, but explicit revocation removes the
|
|
88
|
+
token server-side immediately and reduces the window of misuse
|
|
89
|
+
if the token leaks.
|
|
90
|
+
|
|
91
|
+
### Liveness check — `api.ping`
|
|
92
|
+
|
|
93
|
+
A no-auth health probe used by load balancers, smoke tests, and
|
|
94
|
+
SDK consumer setup checks. The response confirms the platform
|
|
95
|
+
is reachable and reports the build version + database
|
|
96
|
+
connectivity:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const { data } = await api.ping()
|
|
100
|
+
// data.status === 'ok'
|
|
101
|
+
// data.version — platform build version
|
|
102
|
+
// data.database_status === 'connected' | 'disconnected'
|
|
103
|
+
// data.timestamp — ISO 8601
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`api.ping` works on any api instance regardless of session
|
|
107
|
+
state — useful as the first call in tests to detect a stale
|
|
108
|
+
local server before authentication failures muddy the diagnostic.
|
|
109
|
+
|
|
110
|
+
### Sign up
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const { data: user } = await rootApi.auth.signUp({
|
|
114
|
+
email: 'sarah@example.com',
|
|
115
|
+
password: '...',
|
|
116
|
+
first_name: 'Sarah',
|
|
117
|
+
last_name: 'Mitchell',
|
|
118
|
+
})
|
|
119
|
+
// user.id is a UUID; user.confirmed_at is null
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
A newly signed-up user CANNOT sign in until confirmed. The
|
|
123
|
+
production confirmation path is **email-driven**: the platform
|
|
124
|
+
sends a confirmation email; the user clicks a link; the platform
|
|
125
|
+
records `confirmed_at`. **This email flow is not exposed in the
|
|
126
|
+
SDK** — it's a browser+email round-trip outside the API surface.
|
|
127
|
+
|
|
128
|
+
### `api.admin.confirmUser` is a dev fixture, not a production API
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// DEV / VITEST ONLY — do NOT call from production consumer code
|
|
132
|
+
await rootApi.admin.confirmUser(userId)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`api.admin.confirmUser` bypasses the email-confirmation gate. It
|
|
136
|
+
exists so integration tests can simulate a confirmed user without
|
|
137
|
+
a real inbox round-trip. Production consumers MUST NOT use it:
|
|
138
|
+
|
|
139
|
+
- It requires a root Bearer (impossible to obtain in customer
|
|
140
|
+
environments).
|
|
141
|
+
- The email gate exists to prove a real person controls the
|
|
142
|
+
inbox — programmatically bypassing it defeats the human-gate
|
|
143
|
+
invariant (see §3).
|
|
144
|
+
- Future platform releases may restrict it further or remove it
|
|
145
|
+
from the public surface entirely.
|
|
146
|
+
|
|
147
|
+
The method ships in the typed client to support integration-test
|
|
148
|
+
fixtures; treat it as a test-only helper, not a production API.
|
|
149
|
+
|
|
150
|
+
### `api.sessions.verify` — "who am I, where, with what role"
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const { data: ctx } = await api.sessions.verify()
|
|
154
|
+
// ctx.tenant?.slug — current tenant (null if tenantless)
|
|
155
|
+
// ctx.role?.name — role assumed in this session
|
|
156
|
+
// ctx.user — { id, first_name, last_name, ... }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Use cases:
|
|
160
|
+
|
|
161
|
+
1. **Pre-flight check** before performing role-gated operations —
|
|
162
|
+
confirm the Bearer is tenant-scoped + has the expected role.
|
|
163
|
+
2. **Bearer validity probe** — if the call throws, the Bearer is
|
|
164
|
+
expired or revoked; re-mint via `createSession`.
|
|
165
|
+
3. **Cached-Bearer rehydration** — when persisting Bearers across
|
|
166
|
+
processes (integration tests, CLI sessions), verify before
|
|
167
|
+
reusing.
|
|
168
|
+
|
|
169
|
+
For pattern (3), call `verify()` on the stored Bearer and only
|
|
170
|
+
re-mint via `createSession` if the verify call fails.
|
|
171
|
+
|
|
172
|
+
## 2. Tenant
|
|
173
|
+
|
|
174
|
+
A **tenant** is the top-level data sovereignty boundary. Tenants
|
|
175
|
+
own datalakes; datalakes own everything else. A user can belong
|
|
176
|
+
to many tenants; a Bearer is scoped to exactly one.
|
|
177
|
+
|
|
178
|
+
### Two paths to a tenant-scoped Bearer
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
┌─ creator path ─┐ ┌─ invitee path ─┐
|
|
182
|
+
│ │
|
|
183
|
+
api.auth.signUp api.auth.signUp
|
|
184
|
+
│ │
|
|
185
|
+
api.admin.confirmUser* api.admin.confirmUser* (* production: email link)
|
|
186
|
+
│ │
|
|
187
|
+
createSession (no tenantSlug) createSession (no tenantSlug)
|
|
188
|
+
│ │
|
|
189
|
+
api.tenants.create api.invitations.list → .accept
|
|
190
|
+
│ │
|
|
191
|
+
createSession ({ tenantSlug }) createSession ({ tenantSlug })
|
|
192
|
+
▼ ▼
|
|
193
|
+
┌──── tenant-scoped Bearer ────┐
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Both paths converge on a tenant-scoped Bearer. The creator
|
|
197
|
+
becomes `tenant_admin` of the new tenant; the invitee assumes the
|
|
198
|
+
role specified in their invitation.
|
|
199
|
+
|
|
200
|
+
### `api.tenants.create`
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const { data } = await tenantlessApi.tenants.create({
|
|
204
|
+
name: 'Acme Health',
|
|
205
|
+
})
|
|
206
|
+
// data.tenant.id, data.tenant.slug — server-derived
|
|
207
|
+
// data.session_token — see "Gotchas" below
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The slug is server-derived from `name` per the universal slug
|
|
211
|
+
rules (see `type_naming.md` "Never pre-compute the slug
|
|
212
|
+
client-side"). Use the returned slug for downstream calls.
|
|
213
|
+
|
|
214
|
+
**Gotcha**: the response carries a `session_token` field, but this
|
|
215
|
+
is an *incidental* token — do not treat it as a freshly minted
|
|
216
|
+
tenant-scoped Bearer. The authoritative pattern is to call
|
|
217
|
+
`createSession({ ..., tenantSlug })` after `tenants.create`. This
|
|
218
|
+
gives a fresh Bearer with all role + tenant claims correctly
|
|
219
|
+
populated.
|
|
220
|
+
|
|
221
|
+
### `api.tenants.list`
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const { data: tenants } = await api.tenants.list()
|
|
225
|
+
// tenants.data: TenantResponse[] — every tenant the current Bearer can see
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
A root Bearer sees every tenant; a tenant-scoped Bearer typically
|
|
229
|
+
sees only its own. Use this for tenant pickers in apps where a
|
|
230
|
+
user belongs to multiple tenants.
|
|
231
|
+
|
|
232
|
+
## 3. Access control
|
|
233
|
+
|
|
234
|
+
The platform separates **who you are** from **what you can do**.
|
|
235
|
+
Identity (§1) proves the first; this section covers the second.
|
|
236
|
+
|
|
237
|
+
### Roles — two vocabularies, one consumer
|
|
238
|
+
|
|
239
|
+
The platform exposes two distinct role vocabularies on the wire,
|
|
240
|
+
each rooted in a different schema. Knowing which surface
|
|
241
|
+
demands which vocabulary is the difference between a clean
|
|
242
|
+
invite and a 422.
|
|
243
|
+
|
|
244
|
+
#### Vocabulary A: Invitation enum (3 values)
|
|
245
|
+
|
|
246
|
+
What you SUBMIT when creating an invitation. The
|
|
247
|
+
`InvitationRequest.role` field is a closed 3-value enum:
|
|
248
|
+
|
|
249
|
+
| Invitation `role` | Result on accept |
|
|
250
|
+
|-------------------|----------------------------------------|
|
|
251
|
+
| `'member'` | Standard tenant user |
|
|
252
|
+
| `'researcher'` | Tokenized-only access; blocked from regulated schema |
|
|
253
|
+
| `'admin'` | Full tenant management |
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const { data: invite } = await api.invitations.create(tenantSlug, {
|
|
257
|
+
email: 'emma@example.com',
|
|
258
|
+
role: 'admin', // NOT 'tenant_admin' — that's the role-name vocab
|
|
259
|
+
})
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Passing any value outside this 3-set returns a 422 on `/role`.
|
|
263
|
+
|
|
264
|
+
#### Vocabulary B: Role-record names (what `session.role.name` carries)
|
|
265
|
+
|
|
266
|
+
What you READ from a tenant-scoped Bearer's role record. The
|
|
267
|
+
session carries a role-record name string that's distinct from
|
|
268
|
+
the invitation enum:
|
|
269
|
+
|
|
270
|
+
| `session.role.name` | Scope | Source vocab |
|
|
271
|
+
|-------------------------|------------------------------------|--------------|
|
|
272
|
+
| `'tenant_admin'` | Full tenant management | invitation `'admin'` |
|
|
273
|
+
| `'member'` | Standard tenant user | invitation `'member'` |
|
|
274
|
+
| `'tenant_api'` | Programmatic tenant access (most API keys) | provisioned, not invited |
|
|
275
|
+
| `'datalake_admin'` | Full datalake management (requires `datalake_id` on session) | console-mediated |
|
|
276
|
+
| `'researcher'` | Tokenized-only datalake access | invitation `'researcher'` |
|
|
277
|
+
| `'datalake_api'` | Programmatic datalake access | provisioned, not invited |
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const session = await createSession({ baseUrl, email, password, tenantSlug })
|
|
281
|
+
// session.role.name === 'tenant_admin' if accepted from a role: 'admin' invitation
|
|
282
|
+
// session.role.name === 'member' if accepted from a role: 'member' invitation
|
|
283
|
+
// session.role.name === 'researcher' if accepted from a role: 'researcher' invitation
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The asymmetry is deliberate but easy to trip over: the
|
|
287
|
+
invitation enum value `'admin'` becomes the role-record name
|
|
288
|
+
`'tenant_admin'`. The `'member'` and `'researcher'` values
|
|
289
|
+
carry through unchanged.
|
|
290
|
+
|
|
291
|
+
#### Why two vocabularies
|
|
292
|
+
|
|
293
|
+
The Invitation/Membership schema (Vocabulary A) is the
|
|
294
|
+
coarse-grained role assignment surfaced at invite time. The
|
|
295
|
+
Role-record system (Vocabulary B) is the fine-grained policy
|
|
296
|
+
entity that drives row-level access and permission checks at every
|
|
297
|
+
request. They co-exist because the invitation captures the
|
|
298
|
+
caller's intent ("admin-tier user"), while the role record
|
|
299
|
+
captures the platform's enforcement vocabulary
|
|
300
|
+
("tenant-scoped admin role").
|
|
301
|
+
|
|
302
|
+
Datalake-scoped role records (`datalake_admin`, `researcher`
|
|
303
|
+
when used at datalake scope, `datalake_api`) require the
|
|
304
|
+
session to carry a `datalake_id` in addition to a `tenant_id`.
|
|
305
|
+
Most SDK consumers stay at tenant scope; datalake-scoped
|
|
306
|
+
sessions appear in console-mediated flows and are uncommon in
|
|
307
|
+
SDK-consumer code.
|
|
308
|
+
|
|
309
|
+
Reserved roles (`platform_admin`, `system`, `system_api`,
|
|
310
|
+
`bot_*`) bypass tenant-based access isolation and are
|
|
311
|
+
provisioned only by the platform itself — they never appear on
|
|
312
|
+
consumer-minted sessions or invitations and are out of scope
|
|
313
|
+
for SDK consumers.
|
|
314
|
+
|
|
315
|
+
### Inviting humans into a tenant
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// As tenant_admin
|
|
319
|
+
const { data: invite } = await api.invitations.create(tenantSlug, {
|
|
320
|
+
email: 'emma@example.com',
|
|
321
|
+
role: 'member',
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
// As the invitee (tenantless Bearer)
|
|
325
|
+
const { data: invites } = await tenantlessApi.invitations.list()
|
|
326
|
+
const mine = invites.data.find(i => i.tenant.slug === tenantSlug)
|
|
327
|
+
const { data: membership } = await tenantlessApi.invitations.accept(mine.id)
|
|
328
|
+
|
|
329
|
+
// Then mint a tenant-scoped Bearer
|
|
330
|
+
const session = await createSession({ baseUrl, email, password, tenantSlug })
|
|
331
|
+
// session.role.name === 'member'
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The duplicate-invite envelope carries `detail` containing
|
|
335
|
+
"already invited" — see `errors.md` for the canonical 422 shape.
|
|
336
|
+
Treat it as a benign idempotency signal on retry.
|
|
337
|
+
|
|
338
|
+
### Agent authentication — two-tier model
|
|
339
|
+
|
|
340
|
+
Agents (programmatic callers) authenticate via two distinct
|
|
341
|
+
credential types with very different capability profiles:
|
|
342
|
+
|
|
343
|
+
| Credential type | Capability | Created programmatically? |
|
|
344
|
+
|-----------------|---------------------------|------------------------------|
|
|
345
|
+
| **API key** | Write-only, very limited | **Yes** — safe by scope |
|
|
346
|
+
| **PAT** (delegated) | Full per delegating human's role | **No** — human-gated |
|
|
347
|
+
|
|
348
|
+
The rule: **anything that gives an agent broad access is gated by
|
|
349
|
+
a human**. Specifically:
|
|
350
|
+
|
|
351
|
+
- **API keys** can be minted programmatically because their
|
|
352
|
+
capability is intrinsically narrow (write-only, scoped to
|
|
353
|
+
specific endpoints). A leaked API key can deposit data; it
|
|
354
|
+
cannot exfiltrate it.
|
|
355
|
+
- **PATs** delegate a human's full role to an agent. Because the
|
|
356
|
+
resulting agent-Bearer has the same capability as the human,
|
|
357
|
+
PAT issuance MUST require human consent at mint time — the
|
|
358
|
+
human is the gate.
|
|
359
|
+
- **User signup + confirmation** is human-gated (email flow) for
|
|
360
|
+
the same reason: a programmatically-created confirmed user
|
|
361
|
+
could mint Bearers without any human in the loop. The
|
|
362
|
+
email-link confirmation step proves a real person controls the
|
|
363
|
+
inbox.
|
|
364
|
+
|
|
365
|
+
### What the SDK exposes for agent auth
|
|
366
|
+
|
|
367
|
+
**Today the SDK exposes neither credential-lifecycle surface.**
|
|
368
|
+
API key and PAT minting / listing / revocation happen via
|
|
369
|
+
operator-mediated consoles, not via SDK methods. The absence
|
|
370
|
+
of these lifecycle methods from the SDK is the contract.
|
|
371
|
+
|
|
372
|
+
When the SDK does need to *authenticate as* an agent (uncommon in
|
|
373
|
+
fixture code; common in production agent runtimes), the credential
|
|
374
|
+
travels in the appropriate request header — see the credentials
|
|
375
|
+
your operator provides for the exact header convention. A session
|
|
376
|
+
Bearer minted via `createSession` is also accepted for any
|
|
377
|
+
SDK-driven agent workflow.
|
|
378
|
+
|
|
379
|
+
## 4. Lifecycle
|
|
380
|
+
|
|
381
|
+
Bearers and credentials have distinct lifecycles. Summary:
|
|
382
|
+
|
|
383
|
+
| Credential | Expires? | Revocation |
|
|
384
|
+
|------------|---------------------------------------|-------------------|
|
|
385
|
+
| Bearer (session) | Yes — `expires_in` (default 24h, max 30d) | Server-side revoke |
|
|
386
|
+
| API key | No — until explicit revoke | Operator console |
|
|
387
|
+
| PAT | Configurable at issue time | Operator console |
|
|
388
|
+
|
|
389
|
+
Bearer expiry surfaces as a 401 on the next call after expiry —
|
|
390
|
+
re-mint via `createSession`. The SDK does NOT auto-refresh
|
|
391
|
+
Bearers. For long-running agent processes, mint short-lived
|
|
392
|
+
Bearers per logical unit of work rather than holding one for
|
|
393
|
+
hours.
|
|
394
|
+
|
|
395
|
+
## 5. Error envelopes
|
|
396
|
+
|
|
397
|
+
All endpoints in this page emit the standard JSON:API envelope
|
|
398
|
+
documented in `errors.md`. Common rejections:
|
|
399
|
+
|
|
400
|
+
| `source.pointer` / shape | Cause |
|
|
401
|
+
|-------------------------------------------------|----------------------------------------------|
|
|
402
|
+
| `/email` "has already been taken" | `auth.signUp` with an existing email |
|
|
403
|
+
| `/password` "is too short" | password below minimum length |
|
|
404
|
+
| top-level 401 (no envelope) | Bearer missing, expired, or revoked |
|
|
405
|
+
| top-level 403 (no envelope) | role lacks permission for the action |
|
|
406
|
+
| `/email` "already invited" | duplicate `invitations.create` |
|
|
407
|
+
| `/tenant_slug` "not found" | tenant slug doesn't exist or invisible to Bearer |
|
|
408
|
+
|
|
409
|
+
The `tenants.create` 422 is typically `/name` uniqueness — names
|
|
410
|
+
must be unique platform-wide. Re-attempt with a different name or
|
|
411
|
+
treat as "already exists" via `api.tenants.list` lookup.
|
|
412
|
+
|
|
413
|
+
## 6. Gotchas
|
|
414
|
+
|
|
415
|
+
1. **`api.admin.confirmUser` is dev-only.** Production user
|
|
416
|
+
confirmation happens via emailed link, outside the SDK. Treat
|
|
417
|
+
the SDK method as a test fixture; do not ship code that calls
|
|
418
|
+
it from a customer-facing path.
|
|
419
|
+
|
|
420
|
+
2. **The `session_token` returned by `tenants.create` is
|
|
421
|
+
incidental.** Always re-mint a fresh tenant-scoped Bearer via
|
|
422
|
+
`createSession({ ..., tenantSlug })` after creating a tenant.
|
|
423
|
+
This guarantees clean role+tenant claims on the Bearer.
|
|
424
|
+
|
|
425
|
+
3. **Tenantless Bearer is a transient state.** Only two
|
|
426
|
+
operations are useful with it: `tenants.create` (creator path)
|
|
427
|
+
and `invitations.list`/`.accept` (invitee path). For everything
|
|
428
|
+
else, mint a tenant-scoped Bearer first.
|
|
429
|
+
|
|
430
|
+
4. **`api.sessions.verify` is the source of truth for current
|
|
431
|
+
scope.** Don't infer tenant or role from the Bearer string or
|
|
432
|
+
from the `tenants.create` response — call `verify` if you
|
|
433
|
+
need to know.
|
|
434
|
+
|
|
435
|
+
5. **No SDK methods for API key or PAT lifecycle.** Credential
|
|
436
|
+
issuance is operator-console-mediated. If your agent process
|
|
437
|
+
needs an API key, the operator provisions it out-of-band; the
|
|
438
|
+
SDK consumes it but does not create it.
|
|
439
|
+
|
|
440
|
+
6. **Bearers do not auto-refresh.** A long-running process holding
|
|
441
|
+
a Bearer for >24h will see 401s after expiry. Either mint
|
|
442
|
+
short-lived Bearers per logical unit of work, or wrap calls in
|
|
443
|
+
a re-mint-on-401 retry helper local to your code (the SDK does
|
|
444
|
+
not provide one).
|
|
445
|
+
|
|
446
|
+
7. **Invitation acceptance is idempotent at the membership level
|
|
447
|
+
but not at the invitation level.** A second `accept(id)` call
|
|
448
|
+
on the same invitation will 422 (already accepted), but the
|
|
449
|
+
first acceptance's membership is unaffected. Treat retries via
|
|
450
|
+
the "already accepted" 422 the same as "already invited" — a
|
|
451
|
+
benign idempotency signal.
|
|
452
|
+
|
|
453
|
+
8. **Multi-tenant Bearers don't exist.** A Bearer is scoped to
|
|
454
|
+
exactly one tenant at mint time. To act as multiple tenants
|
|
455
|
+
concurrently, mint one Bearer per tenant and use
|
|
456
|
+
`createIsolatedPlatformApi` (see `AGENTS.md`).
|