@productcraft/platform-auth 0.2.0 → 1.0.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 +123 -217
- package/api.md +1716 -0
- package/dist/index.cjs +1809 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1973 -19
- package/dist/index.d.ts +1973 -19
- package/dist/index.js +1744 -8
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +691 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +32 -0
- package/dist/testing.d.ts +32 -0
- package/dist/testing.js +689 -0
- package/dist/testing.js.map +1 -0
- package/manifest.json +1115 -0
- package/package.json +14 -4
package/api.md
ADDED
|
@@ -0,0 +1,1716 @@
|
|
|
1
|
+
# @productcraft/platform-auth API reference
|
|
2
|
+
|
|
3
|
+
<!-- Generated by scripts/gen-sdk.mjs from Specs/platform-auth.json. Do not edit. -->
|
|
4
|
+
<!-- snippet-preamble: import { PlatformAuth } from "@productcraft/platform-auth"; const platform = new PlatformAuth(); -->
|
|
5
|
+
|
|
6
|
+
Every method of `PlatformAuth`, grouped by resource. Types are under the `PlatformAuth` namespace (`import type { PlatformAuth } from "@productcraft/platform-auth"`). Every method takes per-request options last (`{ idempotencyKey, timeout, maxRetries, headers, signal, workspaceId }`).
|
|
7
|
+
|
|
8
|
+
## account
|
|
9
|
+
|
|
10
|
+
### `platform.account.erase(options?)`
|
|
11
|
+
|
|
12
|
+
`DELETE /v1/account` → `void`
|
|
13
|
+
|
|
14
|
+
Delete the calling account. Auto-promotes the oldest admin (or oldest non-self member) to owner in any workspace where the departing account is the sole owner. Linked provider grants that require server-side revocation (Sign in with Apple) are revoked with the provider.
|
|
15
|
+
|
|
16
|
+
| Status | Error class | Codes | When |
|
|
17
|
+
|---|---|---|---|
|
|
18
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
19
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | The session lacks the required action for self-account deletion (defence-in-depth — the route is guarded but the cookie may be present without action access). |
|
|
20
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | No account row exists for the session principal. Should never fire for a valid session — surfaced for SDKs. |
|
|
21
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
await platform.account.erase();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-account)
|
|
28
|
+
|
|
29
|
+
## apiKeys
|
|
30
|
+
|
|
31
|
+
### `platform.apiKeys.self(options?)`
|
|
32
|
+
|
|
33
|
+
`GET /v1/api-keys/self` → `PlatformAuth.ApiKeySelf`
|
|
34
|
+
|
|
35
|
+
Describe the workspace key making this request: its name, workspace, expiry and what it may do in plain sentences. Call it with `Authorization: Bearer pcft_live_…` to test a key right after minting it.
|
|
36
|
+
|
|
37
|
+
| Status | Error class | Codes | When |
|
|
38
|
+
|---|---|---|---|
|
|
39
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | No bearer, or the key is unknown, revoked or expired. |
|
|
40
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `WORKSPACE_KEY_REQUIRED` | Signed in with a session or another token instead of a workspace key (`WORKSPACE_KEY_REQUIRED`). Send the `pcft_live_…` key as the bearer. |
|
|
41
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `PAK_NOT_FOUND` | The key was deleted while the request was in flight (`PAK_NOT_FOUND`). |
|
|
42
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const apiKeySelf = await platform.apiKeys.self();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-api-keys-self)
|
|
49
|
+
|
|
50
|
+
## auth
|
|
51
|
+
|
|
52
|
+
### `platform.auth.logout(params, options?)`
|
|
53
|
+
|
|
54
|
+
`POST /v1/auth/logout` → `void`
|
|
55
|
+
|
|
56
|
+
Logout by revoking refresh token.
|
|
57
|
+
|
|
58
|
+
- Params: `PlatformAuth.AuthLogoutParams`
|
|
59
|
+
- Public: no API key, browser-safe
|
|
60
|
+
|
|
61
|
+
| Status | Error class | Codes | When |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Validation failed or invalid token |
|
|
64
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
await platform.auth.logout({ refresh_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-auth-logout)
|
|
71
|
+
|
|
72
|
+
### `platform.auth.logoutCurrent(options?)`
|
|
73
|
+
|
|
74
|
+
`POST /v1/auth/logout-current` → `void`
|
|
75
|
+
|
|
76
|
+
Revoke the server-side session tied to the caller’s current access token.
|
|
77
|
+
|
|
78
|
+
| Status | Error class | Codes | When |
|
|
79
|
+
|---|---|---|---|
|
|
80
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing or invalid access token |
|
|
81
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
await platform.auth.logoutCurrent();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-auth-logout-current)
|
|
88
|
+
|
|
89
|
+
### `platform.auth.refresh(params, options?)`
|
|
90
|
+
|
|
91
|
+
`POST /v1/auth/refresh` → `PlatformAuth.Token`
|
|
92
|
+
|
|
93
|
+
Refresh access token using refresh token.
|
|
94
|
+
|
|
95
|
+
- Params: `PlatformAuth.AuthRefreshParams`
|
|
96
|
+
- Public: no API key, browser-safe
|
|
97
|
+
|
|
98
|
+
| Status | Error class | Codes | When |
|
|
99
|
+
|---|---|---|---|
|
|
100
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Validation failed or invalid token |
|
|
101
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const token = await platform.auth.refresh({ refresh_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-auth-refresh)
|
|
108
|
+
|
|
109
|
+
### `platform.auth.signin(params, options?)`
|
|
110
|
+
|
|
111
|
+
`POST /v1/auth/signin` → `PlatformAuth.Token`
|
|
112
|
+
|
|
113
|
+
Sign in with email/username and password.
|
|
114
|
+
|
|
115
|
+
Every failed attempt answers `401 INVALID_CREDENTIALS`, whatever went wrong: unknown identifier, wrong password, an account with no password credential, an account locked out through a different identifier, an account an operator has blocked, and a wrong password against a suspended or deactivated account. The responses are deliberately identical in body, timing, DB round trips and Redis ops, so signin is not an account-existence oracle. See core/block/CLAUDE.md.
|
|
116
|
+
|
|
117
|
+
- Params: `PlatformAuth.AuthSigninParams`
|
|
118
|
+
- Public: no API key, browser-safe
|
|
119
|
+
|
|
120
|
+
| Status | Error class | Codes | When |
|
|
121
|
+
|---|---|---|---|
|
|
122
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Validation failed |
|
|
123
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `INVALID_CREDENTIALS` | Invalid credentials (`INVALID_CREDENTIALS`). Returned for every failed attempt, including a correct password offered while the account is locked out or blocked, and a wrong password against a suspended or deactivated account. The body never says which. |
|
|
124
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `ACCOUNT_SUSPENDED`, `ACCOUNT_DEACTIVATED` | The account is suspended (`ACCOUNT_SUSPENDED`) or deactivated (`ACCOUNT_DEACTIVATED`). Only reachable once the caller has proven the password — a wrong password against such an account is a 401. |
|
|
125
|
+
| 429 | `RateLimitError` | `RATE_LIMITED`, `TOO_MANY_ATTEMPTS` | Too many attempts for this identifier (`TOO_MANY_ATTEMPTS`). Two counters produce it, both keyed purely by the submitted identifier: a burst limit of 5 attempts per minute, and a 15-minute lock after 4 failed attempts. Both fire on the same attempt for a registered and an unregistered identifier. Per-IP limit: rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const token = await platform.auth.signin({ identifier: "user@example.com", password: "MySecurePassword123" });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-auth-signin)
|
|
132
|
+
|
|
133
|
+
### `platform.auth.signup(params, options?)`
|
|
134
|
+
|
|
135
|
+
`POST /v1/auth/signup` → `PlatformAuth.Token`
|
|
136
|
+
|
|
137
|
+
Create a new account and sign in.
|
|
138
|
+
|
|
139
|
+
- Params: `PlatformAuth.AuthSignupParams`
|
|
140
|
+
- Public: no API key, browser-safe
|
|
141
|
+
|
|
142
|
+
| Status | Error class | Codes | When |
|
|
143
|
+
|---|---|---|---|
|
|
144
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Validation failed |
|
|
145
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
const token = await platform.auth.signup({ email: "user@example.com", password: "MySecurePassword123", username: "john_doe" });
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-auth-signup)
|
|
152
|
+
|
|
153
|
+
## auth.password
|
|
154
|
+
|
|
155
|
+
### `platform.auth.password.requestReset(params, options?)`
|
|
156
|
+
|
|
157
|
+
`POST /v1/auth/password/request-reset` → `void`
|
|
158
|
+
|
|
159
|
+
Request a password reset email. Neither the response nor its timing reveals whether the email matched an account.
|
|
160
|
+
|
|
161
|
+
- Params: `PlatformAuth.AuthPasswordRequestResetParams`
|
|
162
|
+
- Public: no API key, browser-safe
|
|
163
|
+
|
|
164
|
+
| Status | Error class | Codes | When |
|
|
165
|
+
|---|---|---|---|
|
|
166
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Body failed validation (`email` missing or not an email address). Depends only on the request shape, never on whether an account exists. |
|
|
167
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: more than 10 requests/min from this IP (strict `auth` bucket). `Retry-After` header set. Per-IP limit: rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
await platform.auth.password.requestReset({ email: "user@example.com" });
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-auth-password-request-reset)
|
|
174
|
+
|
|
175
|
+
### `platform.auth.password.reset(params, options?)`
|
|
176
|
+
|
|
177
|
+
`POST /v1/auth/password/reset` → `void`
|
|
178
|
+
|
|
179
|
+
Reset password using the email-delivered reset token.
|
|
180
|
+
|
|
181
|
+
- Params: `PlatformAuth.AuthPasswordResetParams`
|
|
182
|
+
- Public: no API key, browser-safe
|
|
183
|
+
|
|
184
|
+
| Status | Error class | Codes | When |
|
|
185
|
+
|---|---|---|---|
|
|
186
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR`, `PASSWORD_RESET_INVALID` | Body failed validation, the new password fails the password policy, or the reset token is invalid or expired (`PASSWORD_RESET_INVALID`). |
|
|
187
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: more than 10 requests/min from this IP (strict `auth` bucket). `Retry-After` header set. Per-IP limit: rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
await platform.auth.password.reset({ new_password: "MyNewPassword123", token: "123456" });
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-auth-password-reset)
|
|
194
|
+
|
|
195
|
+
## introspect
|
|
196
|
+
|
|
197
|
+
### `platform.introspect.apiKey(options?)`
|
|
198
|
+
|
|
199
|
+
`POST /v1/introspect/api-key` → `PlatformAuth.IntrospectApiKey`
|
|
200
|
+
|
|
201
|
+
Resolve a Platform API Key (PAK) to its workspace + IAM-style policy. Downstream services use this with a 60s LRU cache and evaluate the policy via @repo/authz-nestjs evaluatePolicy().
|
|
202
|
+
|
|
203
|
+
| Status | Error class | Codes | When |
|
|
204
|
+
|---|---|---|---|
|
|
205
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing or malformed `Authorization: Bearer` header, or the PAK is unknown, revoked or expired. |
|
|
206
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
const introspectApiKey = await platform.introspect.apiKey();
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-introspect-api-key)
|
|
213
|
+
|
|
214
|
+
### `platform.introspect.retrieve(options?)`
|
|
215
|
+
|
|
216
|
+
`GET /v1/introspect` → `PlatformAuth.Introspect`
|
|
217
|
+
|
|
218
|
+
Introspect current user with workspace list.
|
|
219
|
+
|
|
220
|
+
| Status | Error class | Codes | When |
|
|
221
|
+
|---|---|---|---|
|
|
222
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: more than 60 requests/min from this IP (default bucket). Anonymous callers, and callers whose session has been signed out, otherwise get 200 with `is_authenticated: false`, never 401. Per-IP limit: rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
const introspect = await platform.introspect.retrieve();
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-introspect)
|
|
229
|
+
|
|
230
|
+
## introspect.workspaces
|
|
231
|
+
|
|
232
|
+
### `platform.introspect.workspaces.retrieve(workspaceId, options?)`
|
|
233
|
+
|
|
234
|
+
`GET /v1/introspect/workspaces/{workspace_id}` → `PlatformAuth.IntrospectWorkspace`
|
|
235
|
+
|
|
236
|
+
Introspect caller's membership + effective policy + enabled services in a workspace.
|
|
237
|
+
|
|
238
|
+
| Status | Error class | Codes | When |
|
|
239
|
+
|---|---|---|---|
|
|
240
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session. Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
241
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found (unknown slug or id). Known workspaces the caller is not a member of answer 200 with `is_member: false`. |
|
|
242
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
const introspectWorkspace = await platform.introspect.workspaces.retrieve("workspace");
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-introspect-workspaces-workspace-id)
|
|
249
|
+
|
|
250
|
+
## me
|
|
251
|
+
|
|
252
|
+
### `platform.me.retrieve(options?)`
|
|
253
|
+
|
|
254
|
+
`GET /v1/me` → `PlatformAuth.MeProfile`
|
|
255
|
+
|
|
256
|
+
Read the caller's account profile.
|
|
257
|
+
|
|
258
|
+
| Status | Error class | Codes | When |
|
|
259
|
+
|---|---|---|---|
|
|
260
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | No session. |
|
|
261
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | No account row for the session principal. Should never fire for a valid session — surfaced for SDKs. |
|
|
262
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
const meProfile = await platform.me.retrieve();
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-me)
|
|
269
|
+
|
|
270
|
+
## me.avatar
|
|
271
|
+
|
|
272
|
+
### `platform.me.avatar.clear(options?)`
|
|
273
|
+
|
|
274
|
+
`DELETE /v1/me/avatar` → `void`
|
|
275
|
+
|
|
276
|
+
Clear the caller's avatar and delete the stored object.
|
|
277
|
+
|
|
278
|
+
| Status | Error class | Codes | When |
|
|
279
|
+
|---|---|---|---|
|
|
280
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | No session. |
|
|
281
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
await platform.me.avatar.clear();
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-me-avatar)
|
|
288
|
+
|
|
289
|
+
### `platform.me.avatar.upload(params, options?)`
|
|
290
|
+
|
|
291
|
+
`POST /v1/me/avatar` → `PlatformAuth.AvatarUrl`
|
|
292
|
+
|
|
293
|
+
Upload (or replace) the caller's avatar. PNG/JPEG/WEBP only — SVG is rejected because user-uploaded SVG from a productcraft.co origin is a stored-XSS vector. ≤2 MB. Magic bytes are sniffed; a mislabeled file is rejected.
|
|
294
|
+
|
|
295
|
+
- Params: `PlatformAuth.MeAvatarUploadParams`
|
|
296
|
+
|
|
297
|
+
| Status | Error class | Codes | When |
|
|
298
|
+
|---|---|---|---|
|
|
299
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Invalid file: wrong mimetype, oversized, or content does not match the declared type. |
|
|
300
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | No session. |
|
|
301
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
const avatarUrl = await platform.me.avatar.upload({ file: new File(["…"], "image.png", { type: "image/png" }) });
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-me-avatar)
|
|
308
|
+
|
|
309
|
+
## me.hiddenGuides
|
|
310
|
+
|
|
311
|
+
### `platform.me.hiddenGuides.hide(key, options?)`
|
|
312
|
+
|
|
313
|
+
`PUT /v1/me/hidden-guides/{key}` → `PlatformAuth.HiddenGuide`
|
|
314
|
+
|
|
315
|
+
Hide a getting-started guide for the caller.
|
|
316
|
+
|
|
317
|
+
Idempotent: hiding a guide that is already hidden returns it unchanged, with its first `hidden_at`. At most 200 are kept; hiding one more drops the oldest.
|
|
318
|
+
|
|
319
|
+
| Status | Error class | Codes | When |
|
|
320
|
+
|---|---|---|---|
|
|
321
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | The key is not a valid guide key. |
|
|
322
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | No session. |
|
|
323
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | The caller authenticated with a non-human principal (e.g. a workspace API key). Hidden guides are a person's console preference. |
|
|
324
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
const hiddenGuide = await platform.me.hiddenGuides.hide("key");
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#put-v1-me-hidden-guides-key)
|
|
331
|
+
|
|
332
|
+
### `platform.me.hiddenGuides.list(options?)`
|
|
333
|
+
|
|
334
|
+
`GET /v1/me/hidden-guides` → `PlatformAuth.HiddenGuides`
|
|
335
|
+
|
|
336
|
+
List the getting-started guides the caller has hidden.
|
|
337
|
+
|
|
338
|
+
Every guide the caller has hidden in the console, newest first. Hiding is presentation only: the setup status behind a guide is unchanged.
|
|
339
|
+
|
|
340
|
+
| Status | Error class | Codes | When |
|
|
341
|
+
|---|---|---|---|
|
|
342
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | No session. |
|
|
343
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | The caller authenticated with a non-human principal (e.g. a workspace API key). Hidden guides are a person's console preference. |
|
|
344
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
const hiddenGuides = await platform.me.hiddenGuides.list();
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-me-hidden-guides)
|
|
351
|
+
|
|
352
|
+
### `platform.me.hiddenGuides.reset(options?)`
|
|
353
|
+
|
|
354
|
+
`DELETE /v1/me/hidden-guides` → `void`
|
|
355
|
+
|
|
356
|
+
Show every getting-started guide again.
|
|
357
|
+
|
|
358
|
+
Removes every hidden guide for the caller, `all` included. Idempotent.
|
|
359
|
+
|
|
360
|
+
| Status | Error class | Codes | When |
|
|
361
|
+
|---|---|---|---|
|
|
362
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | No session. |
|
|
363
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | The caller authenticated with a non-human principal (e.g. a workspace API key). Hidden guides are a person's console preference. |
|
|
364
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
await platform.me.hiddenGuides.reset();
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-me-hidden-guides)
|
|
371
|
+
|
|
372
|
+
### `platform.me.hiddenGuides.unhide(key, options?)`
|
|
373
|
+
|
|
374
|
+
`DELETE /v1/me/hidden-guides/{key}` → `void`
|
|
375
|
+
|
|
376
|
+
Show a hidden getting-started guide again.
|
|
377
|
+
|
|
378
|
+
Idempotent: showing a guide that is not hidden is a no-op.
|
|
379
|
+
|
|
380
|
+
| Status | Error class | Codes | When |
|
|
381
|
+
|---|---|---|---|
|
|
382
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | The key is not a valid guide key. |
|
|
383
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | No session. |
|
|
384
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | The caller authenticated with a non-human principal (e.g. a workspace API key). Hidden guides are a person's console preference. |
|
|
385
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
await platform.me.hiddenGuides.unhide("key");
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-me-hidden-guides-key)
|
|
392
|
+
|
|
393
|
+
## me.profile
|
|
394
|
+
|
|
395
|
+
### `platform.me.profile.update(params?, options?)`
|
|
396
|
+
|
|
397
|
+
`PATCH /v1/me/profile` → `PlatformAuth.MeProfile`
|
|
398
|
+
|
|
399
|
+
Update the caller's profile. Currently only `displayName` is editable. An empty body is a no-op that returns the current profile. Cookie/JWT only — this is a human-self-service surface, not a PAK-mutation surface.
|
|
400
|
+
|
|
401
|
+
- Params: `PlatformAuth.MeProfileUpdateParams`
|
|
402
|
+
|
|
403
|
+
| Status | Error class | Codes | When |
|
|
404
|
+
|---|---|---|---|
|
|
405
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Validation failed (e.g. displayName outside 1-128 chars). |
|
|
406
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | No session. |
|
|
407
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | The caller authenticated with a non-human principal (e.g. PAK). Profile is per-human; only the human owner can mutate their own row. |
|
|
408
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
const meProfile = await platform.me.profile.update({ display_name: "string" });
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#patch-v1-me-profile)
|
|
415
|
+
|
|
416
|
+
## oauth.connections
|
|
417
|
+
|
|
418
|
+
### `platform.oauth.connections.delete(grantId, options?)`
|
|
419
|
+
|
|
420
|
+
`DELETE /v1/oauth/connections/{grant_id}` → `void`
|
|
421
|
+
|
|
422
|
+
Disconnect a client.
|
|
423
|
+
|
|
424
|
+
Revokes the whole grant family — the grant, its authorization codes, its refresh tokens and every live access token — in one transaction.
|
|
425
|
+
|
|
426
|
+
| Status | Error class | Codes | When |
|
|
427
|
+
|---|---|---|---|
|
|
428
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
429
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | No such connection for this human. |
|
|
430
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
await platform.oauth.connections.delete("3f0c1a2b-4c5d-4e6f-8a9b-0c1d2e3f4a5b");
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-oauth-connections-grant-id)
|
|
437
|
+
|
|
438
|
+
### `platform.oauth.connections.list(params?, options?)`
|
|
439
|
+
|
|
440
|
+
`GET /v1/oauth/connections` → `PagePromise<PlatformAuth.ConnectedClient>`
|
|
441
|
+
|
|
442
|
+
List clients this human has connected.
|
|
443
|
+
|
|
444
|
+
The user-facing half of revocation: which third-party clients hold a grant, in which workspace, carrying which policy.
|
|
445
|
+
|
|
446
|
+
- Params: `PlatformAuth.OauthConnectionListParams`
|
|
447
|
+
- Paginated: `for await` iterates every item; `await` gives one page
|
|
448
|
+
|
|
449
|
+
| Status | Error class | Codes | When |
|
|
450
|
+
|---|---|---|---|
|
|
451
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Malformed `cursor`. |
|
|
452
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
453
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
for await (const connection of platform.oauth.connections.list()) {
|
|
457
|
+
console.log(connection);
|
|
458
|
+
}
|
|
459
|
+
const page = await platform.oauth.connections.list({}, { timeout: 5_000 });
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-oauth-connections)
|
|
463
|
+
|
|
464
|
+
## oauth.workspaces.connections
|
|
465
|
+
|
|
466
|
+
### `platform.oauth.workspaces.connections.delete(workspaceId, grantId, options?)`
|
|
467
|
+
|
|
468
|
+
`DELETE /v1/oauth/workspaces/{workspace_id}/connections/{grant_id}` → `void`
|
|
469
|
+
|
|
470
|
+
Disconnect a client on behalf of the workspace.
|
|
471
|
+
|
|
472
|
+
Revokes the whole grant family for a grant made by any member of this workspace — the lever a workspace owner needs when an employee leaves and their MCP client is still running. Requires `workspace.connection.revoke`.
|
|
473
|
+
|
|
474
|
+
| Status | Error class | Codes | When |
|
|
475
|
+
|---|---|---|---|
|
|
476
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
477
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | Not a member, or lacks `workspace.connection.revoke`. |
|
|
478
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | No such connection in this workspace. |
|
|
479
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
await platform.oauth.workspaces.connections.delete("3f0c1a2b-4c5d-4e6f-8a9b-0c1d2e3f4a5b", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6");
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-oauth-workspaces-workspace-id-connections-grant-id)
|
|
486
|
+
|
|
487
|
+
### `platform.oauth.workspaces.connections.list(workspaceId, params?, options?)`
|
|
488
|
+
|
|
489
|
+
`GET /v1/oauth/workspaces/{workspace_id}/connections` → `PagePromise<PlatformAuth.ConnectedClient>`
|
|
490
|
+
|
|
491
|
+
List every client connected to a workspace.
|
|
492
|
+
|
|
493
|
+
The workspace-side half of revocation, for owners and admins: every grant made against this workspace, by any member, including grants made by people who have since left. Requires `workspace.connection.read`.
|
|
494
|
+
|
|
495
|
+
- Params: `PlatformAuth.OauthWorkspaceConnectionListParams`
|
|
496
|
+
- Paginated: `for await` iterates every item; `await` gives one page
|
|
497
|
+
|
|
498
|
+
| Status | Error class | Codes | When |
|
|
499
|
+
|---|---|---|---|
|
|
500
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Malformed `cursor`. |
|
|
501
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
502
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | Not a member, or lacks `workspace.connection.read`. |
|
|
503
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
for await (const connection of platform.oauth.workspaces.connections.list("3f0c1a2b-4c5d-4e6f-8a9b-0c1d2e3f4a5b")) {
|
|
507
|
+
console.log(connection);
|
|
508
|
+
}
|
|
509
|
+
const page = await platform.oauth.workspaces.connections.list("3f0c1a2b-4c5d-4e6f-8a9b-0c1d2e3f4a5b", {}, { timeout: 5_000 });
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-oauth-workspaces-workspace-id-connections)
|
|
513
|
+
|
|
514
|
+
### `platform.oauth.workspaces.connections.restore(workspaceId, grantId, options?)`
|
|
515
|
+
|
|
516
|
+
`POST /v1/oauth/workspaces/{workspace_id}/connections/{grant_id}/restore` → `void`
|
|
517
|
+
|
|
518
|
+
Lift a workspace revocation so the member may reconnect.
|
|
519
|
+
|
|
520
|
+
A workspace revocation is durable: the member re-running the connect flow does not undo it, so an administrator has to. This clears the block only — every token minted under the old grant stays revoked and the member still has to approve a fresh consent screen. Requires `workspace.connection.revoke`.
|
|
521
|
+
|
|
522
|
+
| Status | Error class | Codes | When |
|
|
523
|
+
|---|---|---|---|
|
|
524
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session, or a session that has been signed out (`SESSION_REVOKED`). |
|
|
525
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | Not a member, or lacks `workspace.connection.revoke`. |
|
|
526
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | No such connection in this workspace. |
|
|
527
|
+
| 409 | `ConflictError` | `CONFLICT` | The connection was not revoked by an administrator — there is nothing to lift. |
|
|
528
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
529
|
+
|
|
530
|
+
```ts
|
|
531
|
+
await platform.oauth.workspaces.connections.restore("3f0c1a2b-4c5d-4e6f-8a9b-0c1d2e3f4a5b", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6");
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-oauth-workspaces-workspace-id-connections-grant-id-restore)
|
|
535
|
+
|
|
536
|
+
## sessions
|
|
537
|
+
|
|
538
|
+
### `platform.sessions.delete(id, options?)`
|
|
539
|
+
|
|
540
|
+
`DELETE /v1/session/{id}` → `PlatformAuth.RevokeSession`
|
|
541
|
+
|
|
542
|
+
Revoke one of the caller's own sessions. Takes effect immediately — the refresh token stops working straight away, not at the end of its window.
|
|
543
|
+
|
|
544
|
+
| Status | Error class | Codes | When |
|
|
545
|
+
|---|---|---|---|
|
|
546
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | `id` is not a UUID. |
|
|
547
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Unauthorized |
|
|
548
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | No such session, or it belongs to somebody else — deliberately the same response, so this cannot be used to probe which session ids exist. |
|
|
549
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
550
|
+
|
|
551
|
+
```ts
|
|
552
|
+
const revokeSession = await platform.sessions.delete("3f0c1a2b-4c5d-4e6f-8a9b-0c1d2e3f4a5b");
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-session-id)
|
|
556
|
+
|
|
557
|
+
### `platform.sessions.list(options?)`
|
|
558
|
+
|
|
559
|
+
`GET /v1/session` → `PlatformAuth.SessionListResponse`
|
|
560
|
+
|
|
561
|
+
List active sessions.
|
|
562
|
+
|
|
563
|
+
| Status | Error class | Codes | When |
|
|
564
|
+
|---|---|---|---|
|
|
565
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Unauthorized |
|
|
566
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
567
|
+
|
|
568
|
+
```ts
|
|
569
|
+
const result = await platform.sessions.list();
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-session)
|
|
573
|
+
|
|
574
|
+
### `platform.sessions.revokeOthers(options?)`
|
|
575
|
+
|
|
576
|
+
`POST /v1/session/revoke-others` → `PlatformAuth.RevokeOtherSessions`
|
|
577
|
+
|
|
578
|
+
Sign out every other session of the caller, keeping the one this request was made with. Takes effect immediately for the revoked sessions. Idempotent: a repeat call revokes 0.
|
|
579
|
+
|
|
580
|
+
| Status | Error class | Codes | When |
|
|
581
|
+
|---|---|---|---|
|
|
582
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Unauthorized |
|
|
583
|
+
| 409 | `ConflictError` | `CONFLICT` | The access token maps to no session (minted before sessions tracked token ids), so the current session cannot be kept. Sign in again. |
|
|
584
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
585
|
+
|
|
586
|
+
```ts
|
|
587
|
+
const revokeOtherSessions = await platform.sessions.revokeOthers();
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-session-revoke-others)
|
|
591
|
+
|
|
592
|
+
## stats
|
|
593
|
+
|
|
594
|
+
### `platform.stats.retrieve(params?, options?)`
|
|
595
|
+
|
|
596
|
+
`GET /v1/workspaces/{workspace_id}/stats` → `PlatformAuth.WorkspaceStats`
|
|
597
|
+
|
|
598
|
+
Workspace stats.
|
|
599
|
+
|
|
600
|
+
Member count, active platform API keys, admin changes in the window with a daily series, the last admin change and the last API-key use, for the console dashboard.
|
|
601
|
+
|
|
602
|
+
Admin changes are workspace audit events that record a mutation. Reads (any action ending `.read`), sign-ins (`auth.signin.succeeded`) and MCP tool-call rows (`workspace.mcp_tool.*`) are not counted, so opening the console does not move the numbers. Days are UTC. Counts and timestamps only: no actor identifiers. Cached for up to 60 seconds (`generated_at`).
|
|
603
|
+
|
|
604
|
+
- Params: `PlatformAuth.StatsRetrieveParams`
|
|
605
|
+
|
|
606
|
+
| Status | Error class | Codes | When |
|
|
607
|
+
|---|---|---|---|
|
|
608
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | `window` is not one of 1d, 7d, 30d, or `workspaceId` is the slug of a workspace the caller can read rather than its UUID. |
|
|
609
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
610
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of the workspace (`NOT_A_MEMBER`) or lacks `workspace.audit.read` on it. |
|
|
611
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found: no workspace has this id (or slug). Same answer as the service's other workspace routes. |
|
|
612
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
const workspaceStats = await platform.stats.retrieve();
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-id-stats)
|
|
619
|
+
|
|
620
|
+
## verification
|
|
621
|
+
|
|
622
|
+
### `platform.verification.resend(options?)`
|
|
623
|
+
|
|
624
|
+
`POST /v1/verification/resend` → `PlatformAuth.Feedback`
|
|
625
|
+
|
|
626
|
+
Resend verification email.
|
|
627
|
+
|
|
628
|
+
| Status | Error class | Codes | When |
|
|
629
|
+
|---|---|---|---|
|
|
630
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Unauthorized |
|
|
631
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
632
|
+
|
|
633
|
+
```ts
|
|
634
|
+
const feedback = await platform.verification.resend();
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-verification-resend)
|
|
638
|
+
|
|
639
|
+
### `platform.verification.verify(params, options?)`
|
|
640
|
+
|
|
641
|
+
`POST /v1/verification/verify` → `PlatformAuth.Feedback`
|
|
642
|
+
|
|
643
|
+
Verify an email code.
|
|
644
|
+
|
|
645
|
+
- Params: `PlatformAuth.VerificationVerifyParams`
|
|
646
|
+
- Public: no API key, browser-safe
|
|
647
|
+
|
|
648
|
+
| Status | Error class | Codes | When |
|
|
649
|
+
|---|---|---|---|
|
|
650
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Invalid or expired verification code |
|
|
651
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
652
|
+
|
|
653
|
+
```ts
|
|
654
|
+
const feedback = await platform.verification.verify({ code: "a1b2c3d4…" });
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-verification-verify)
|
|
658
|
+
|
|
659
|
+
## wellKnown
|
|
660
|
+
|
|
661
|
+
### `platform.wellKnown.jwks(options?)`
|
|
662
|
+
|
|
663
|
+
`GET /.well-known/jwks.json` → `PlatformAuth.Jwks`
|
|
664
|
+
|
|
665
|
+
Public JWKS for verifying platform-issued JWTs (issuer `https://api.auth.productcraft.co`).
|
|
666
|
+
|
|
667
|
+
- Public: no API key, browser-safe
|
|
668
|
+
|
|
669
|
+
| Status | Error class | Codes | When |
|
|
670
|
+
|---|---|---|---|
|
|
671
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
672
|
+
|
|
673
|
+
```ts
|
|
674
|
+
const jwks = await platform.wellKnown.jwks();
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-well-known-jwks-json)
|
|
678
|
+
|
|
679
|
+
### `platform.wellKnown.oauthAuthorizationServer(options?)`
|
|
680
|
+
|
|
681
|
+
`GET /.well-known/oauth-authorization-server` → `PlatformAuth.AuthorizationServerMetadata`
|
|
682
|
+
|
|
683
|
+
OAuth 2.1 authorization server metadata (RFC 8414).
|
|
684
|
+
|
|
685
|
+
Discovery document for third-party clients (Claude Desktop, Cursor, the ProductCraft MCP server). PKCE with `S256` is mandatory; no other code-challenge method is advertised or accepted. `scopes_supported` lists **presets**, not scopes: authority is the policy document on the grant (`authority_model`).
|
|
686
|
+
|
|
687
|
+
- Public: no API key, browser-safe
|
|
688
|
+
|
|
689
|
+
| Status | Error class | Codes | When |
|
|
690
|
+
|---|---|---|---|
|
|
691
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
692
|
+
|
|
693
|
+
```ts
|
|
694
|
+
const authorizationServerMetadata = await platform.wellKnown.oauthAuthorizationServer();
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-well-known-oauth-authorization-server)
|
|
698
|
+
|
|
699
|
+
## workspaces
|
|
700
|
+
|
|
701
|
+
### `platform.workspaces.create(params, options?)`
|
|
702
|
+
|
|
703
|
+
`POST /v1/workspaces` → `PlatformAuth.Workspace`
|
|
704
|
+
|
|
705
|
+
Create a new workspace. Cookie/JWT only — PAKs are workspace-scoped credentials and cannot create new workspaces.
|
|
706
|
+
|
|
707
|
+
- Params: `PlatformAuth.WorkspaceCreateParams`
|
|
708
|
+
- Idempotent: an `Idempotency-Key` is sent automatically and reused on retries
|
|
709
|
+
|
|
710
|
+
| Status | Error class | Codes | When |
|
|
711
|
+
|---|---|---|---|
|
|
712
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
713
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | Caller is a PAK (`pcft_live_*`). Workspace creation requires a human-issued session. |
|
|
714
|
+
| 409 | `ConflictError` | `CONFLICT`, `IDEMPOTENCY_KEY_REUSE`, `IDEMPOTENCY_IN_PROGRESS` | Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key). |
|
|
715
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
716
|
+
|
|
717
|
+
```ts
|
|
718
|
+
const workspace = await platform.workspaces.create({ display_name: "Acme Corp", slug: "acme-corp" });
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-workspaces)
|
|
722
|
+
|
|
723
|
+
### `platform.workspaces.delete(workspaceSlug, options?)`
|
|
724
|
+
|
|
725
|
+
`DELETE /v1/workspaces/{workspace_slug}` → `void`
|
|
726
|
+
|
|
727
|
+
Delete a workspace (requires workspace.delete). Cookie/JWT only — irreversible workspace destruction needs a human-attributed audit row.
|
|
728
|
+
|
|
729
|
+
| Status | Error class | Codes | When |
|
|
730
|
+
|---|---|---|---|
|
|
731
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session, or a session that has been signed out (`SESSION_REVOKED`). |
|
|
732
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | Caller is a PAK (`pcft_live_*`). Workspace deletion requires a human-issued session. |
|
|
733
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
734
|
+
|
|
735
|
+
```ts
|
|
736
|
+
await platform.workspaces.delete("workspace");
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-workspaces-workspace-slug)
|
|
740
|
+
|
|
741
|
+
### `platform.workspaces.list(params?, options?)`
|
|
742
|
+
|
|
743
|
+
`GET /v1/workspaces` → `PagePromise<PlatformAuth.WorkspaceListItem>`
|
|
744
|
+
|
|
745
|
+
List workspaces the current user belongs to.
|
|
746
|
+
|
|
747
|
+
- Params: `PlatformAuth.WorkspaceListParams`
|
|
748
|
+
- Paginated: `for await` iterates every item; `await` gives one page
|
|
749
|
+
|
|
750
|
+
| Status | Error class | Codes | When |
|
|
751
|
+
|---|---|---|---|
|
|
752
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Malformed `cursor`. |
|
|
753
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing or invalid session. |
|
|
754
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
755
|
+
|
|
756
|
+
```ts
|
|
757
|
+
for await (const workspace of platform.workspaces.list()) {
|
|
758
|
+
console.log(workspace);
|
|
759
|
+
}
|
|
760
|
+
const page = await platform.workspaces.list({}, { timeout: 5_000 });
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces)
|
|
764
|
+
|
|
765
|
+
### `platform.workspaces.retrieve(workspaceSlug, options?)`
|
|
766
|
+
|
|
767
|
+
`GET /v1/workspaces/{workspace_slug}` → `PlatformAuth.Workspace`
|
|
768
|
+
|
|
769
|
+
Get workspace details.
|
|
770
|
+
|
|
771
|
+
| Status | Error class | Codes | When |
|
|
772
|
+
|---|---|---|---|
|
|
773
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing or invalid session. |
|
|
774
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`). |
|
|
775
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found (unknown slug or id). |
|
|
776
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
777
|
+
|
|
778
|
+
```ts
|
|
779
|
+
const workspace = await platform.workspaces.retrieve("workspace");
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug)
|
|
783
|
+
|
|
784
|
+
### `platform.workspaces.update(workspaceSlug, params?, options?)`
|
|
785
|
+
|
|
786
|
+
`PATCH /v1/workspaces/{workspace_slug}` → `PlatformAuth.Workspace`
|
|
787
|
+
|
|
788
|
+
Update workspace display name, slug, or settings. Slug renames broadcast a `workspace.renamed` event so denormalised consumers (waitlist-api) sync.
|
|
789
|
+
|
|
790
|
+
- Params: `PlatformAuth.WorkspaceUpdateParams`
|
|
791
|
+
|
|
792
|
+
| Status | Error class | Codes | When |
|
|
793
|
+
|---|---|---|---|
|
|
794
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Invalid slug or display name. |
|
|
795
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
796
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found. |
|
|
797
|
+
| 409 | `ConflictError` | `CONFLICT` | Slug already taken by another workspace. |
|
|
798
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
799
|
+
|
|
800
|
+
```ts
|
|
801
|
+
const workspace = await platform.workspaces.update("workspace", { display_name: "string" });
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#patch-v1-workspaces-workspace-slug)
|
|
805
|
+
|
|
806
|
+
## workspaces.apiKeys
|
|
807
|
+
|
|
808
|
+
### `platform.workspaces.apiKeys.create(workspaceSlug, params, options?)`
|
|
809
|
+
|
|
810
|
+
`POST /v1/workspaces/{workspace_slug}/api-keys` → `PlatformAuth.MintPak`
|
|
811
|
+
|
|
812
|
+
Mint a new PAK. Choose what it can do with `preset` (a ready-made set such as `mail-send`) or `policy_ids` (managed policies you wrote), never both. The plaintext `token` is returned ONCE — store it now, then call `GET /v1/api-keys/self` with it to check what it can do. The minter can grant at most their own effective permissions. Cookie/JWT only — PAKs cannot mint other PAKs. Send an `Idempotency-Key` to make a retry safe: a retry of a mint that succeeded mints nothing and answers `409 PAK_ALREADY_MINTED` with that key's `id` (the token itself is never cached).
|
|
813
|
+
|
|
814
|
+
- Params: `PlatformAuth.WorkspaceApiKeyCreateParams`
|
|
815
|
+
- Idempotent: an `Idempotency-Key` is sent automatically and reused on retries
|
|
816
|
+
|
|
817
|
+
| Status | Error class | Codes | When |
|
|
818
|
+
|---|---|---|---|
|
|
819
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR`, `PAK_NAME_REQUIRED`, `PAK_NAME_TOO_LONG` | Body failed validation (`VALIDATION_ERROR`): neither or both of `policy_ids` and `preset`, an unknown `preset`, an empty `policy_ids`, more than 25 policies, or a blank/overlong `name` (`PAK_NAME_REQUIRED` / `PAK_NAME_TOO_LONG`). |
|
|
820
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing, malformed or invalid session. Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
821
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is a PAK (`pcft_live_*`) — mint requires a human-issued session; or is not a member (`NOT_A_MEMBER`), lacks `workspace.apikey.create`, or cannot grant one or more actions in the chosen preset or policies. |
|
|
822
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `POLICY_NOT_FOUND` | Workspace not found, or one of `policy_ids` does not exist in this workspace (`POLICY_NOT_FOUND`). |
|
|
823
|
+
| 409 | `ConflictError` | `CONFLICT`, `PAK_ALREADY_MINTED`, `IDEMPOTENCY_KEY_REUSE`, `IDEMPOTENCY_IN_PROGRESS` | `PAK_ALREADY_MINTED`: this `Idempotency-Key` already minted a key (its `id` is in `details`); the token was shown once, so rotate that key for a new one. Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key). |
|
|
824
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
825
|
+
|
|
826
|
+
```ts
|
|
827
|
+
const mintPak = await platform.workspaces.apiKeys.create("workspace", { name: "Production social client" });
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-workspaces-workspace-slug-api-keys)
|
|
831
|
+
|
|
832
|
+
### `platform.workspaces.apiKeys.delete(workspaceSlug, id, params?, options?)`
|
|
833
|
+
|
|
834
|
+
`DELETE /v1/workspaces/{workspace_slug}/api-keys/{id}` → `void`
|
|
835
|
+
|
|
836
|
+
Revoke a PAK. Default behaviour: introspect returns null on the next upstream call; downstream services fail-closed within the 60s cache TTL window. Pass `?force=true` to additionally publish a Redis fan-out that evicts the matching cache entry across every PAK-consuming service immediately — use it when rotating a leaked PAK and the 60s window matters.
|
|
837
|
+
|
|
838
|
+
- Params: `PlatformAuth.WorkspaceApiKeyDeleteParams`
|
|
839
|
+
|
|
840
|
+
| Status | Error class | Codes | When |
|
|
841
|
+
|---|---|---|---|
|
|
842
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | `id` is not a UUID. |
|
|
843
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing, malformed or invalid session, or a PAK bearer (this route accepts cookie/JWT sessions only). Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
844
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.apikey.delete`. |
|
|
845
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `PAK_NOT_FOUND` | Workspace not found, or PAK not found in this workspace (`PAK_NOT_FOUND`). Revoking an already-revoked PAK is a 204 no-op. |
|
|
846
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
847
|
+
|
|
848
|
+
```ts
|
|
849
|
+
await platform.workspaces.apiKeys.delete("workspace", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6");
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-workspaces-workspace-slug-api-keys-id)
|
|
853
|
+
|
|
854
|
+
### `platform.workspaces.apiKeys.list(workspaceSlug, params?, options?)`
|
|
855
|
+
|
|
856
|
+
`GET /v1/workspaces/{workspace_slug}/api-keys` → `PagePromise<PlatformAuth.PublicApiKey>`
|
|
857
|
+
|
|
858
|
+
List the API keys in this workspace.
|
|
859
|
+
|
|
860
|
+
Newest first, as the standard cursor-paginated `{data, pagination}` envelope (20 per page, at most 100). Plaintext tokens are never returned, only prefixes and metadata.
|
|
861
|
+
|
|
862
|
+
**Deprecated legacy shape:** a request that sends neither `limit` nor `cursor` gets a bare JSON array of every key (capped at 1000), unless its `User-Agent` is a ProductCraft SDK (`productcraft-…`), which always gets the envelope. The array is kept only for `@productcraft/platform-auth` 0.0.x clients and is removed in the platform API's next major version; send `limit` (or use an SDK).
|
|
863
|
+
|
|
864
|
+
- Params: `PlatformAuth.WorkspaceApiKeyListParams`
|
|
865
|
+
- Paginated: `for await` iterates every item; `await` gives one page
|
|
866
|
+
|
|
867
|
+
| Status | Error class | Codes | When |
|
|
868
|
+
|---|---|---|---|
|
|
869
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Malformed `cursor`, or `include_revoked` is not `true` or `false` (`VALIDATION_ERROR`). |
|
|
870
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
871
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
872
|
+
|
|
873
|
+
```ts
|
|
874
|
+
for await (const apiKey of platform.workspaces.apiKeys.list("workspace")) {
|
|
875
|
+
console.log(apiKey);
|
|
876
|
+
}
|
|
877
|
+
const page = await platform.workspaces.apiKeys.list("workspace", {}, { timeout: 5_000 });
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-api-keys)
|
|
881
|
+
|
|
882
|
+
### `platform.workspaces.apiKeys.preview(workspaceSlug, params?, options?)`
|
|
883
|
+
|
|
884
|
+
`POST /v1/workspaces/{workspace_slug}/api-keys/preview` → `PlatformAuth.PakGrantPreview`
|
|
885
|
+
|
|
886
|
+
Describe the key a mint with this `preset` or these `policy_ids` would create, without creating anything: what it could do in plain sentences, and whether you may mint it. Read-only, so it takes no `Idempotency-Key`. Powers the review step of the console key wizard.
|
|
887
|
+
|
|
888
|
+
- Params: `PlatformAuth.WorkspaceApiKeyPreviewParams`
|
|
889
|
+
|
|
890
|
+
| Status | Error class | Codes | When |
|
|
891
|
+
|---|---|---|---|
|
|
892
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Body failed validation (`VALIDATION_ERROR`): neither or both of `policy_ids` and `preset`, an unknown `preset`, or an empty or oversized `policy_ids`. |
|
|
893
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing, malformed or invalid session. Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
894
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is a PAK, is not a member (`NOT_A_MEMBER`), or lacks `workspace.apikey.create`. A key you could not mint is NOT a 403 here: it comes back with `grantable: false`. |
|
|
895
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `POLICY_NOT_FOUND` | Workspace not found, or one of `policy_ids` does not exist in this workspace (`POLICY_NOT_FOUND`). |
|
|
896
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
897
|
+
|
|
898
|
+
```ts
|
|
899
|
+
const pakGrantPreview = await platform.workspaces.apiKeys.preview("workspace", { policy_ids: ["11111111-1111-1111-1111-111111111111"] });
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-workspaces-workspace-slug-api-keys-preview)
|
|
903
|
+
|
|
904
|
+
### `platform.workspaces.apiKeys.retrieve(workspaceSlug, id, options?)`
|
|
905
|
+
|
|
906
|
+
`GET /v1/workspaces/{workspace_slug}/api-keys/{id}` → `PlatformAuth.PublicApiKey`
|
|
907
|
+
|
|
908
|
+
Get a single PAK by id.
|
|
909
|
+
|
|
910
|
+
| Status | Error class | Codes | When |
|
|
911
|
+
|---|---|---|---|
|
|
912
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | `id` is not a UUID. |
|
|
913
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, malformed or invalid session, or a PAK bearer (this route accepts cookie/JWT sessions only). |
|
|
914
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.apikey.read`. |
|
|
915
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `PAK_NOT_FOUND` | Workspace not found, or PAK not found in this workspace (`PAK_NOT_FOUND`). |
|
|
916
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
917
|
+
|
|
918
|
+
```ts
|
|
919
|
+
const publicApiKey = await platform.workspaces.apiKeys.retrieve("workspace", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6");
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-api-keys-id)
|
|
923
|
+
|
|
924
|
+
### `platform.workspaces.apiKeys.rotate(workspaceSlug, id, params?, options?)`
|
|
925
|
+
|
|
926
|
+
`POST /v1/workspaces/{workspace_slug}/api-keys/{id}/rotate` → `PlatformAuth.RotatePak`
|
|
927
|
+
|
|
928
|
+
Mint a replacement for an existing PAK, carrying its name, description and policy bindings over. The outgoing key keeps working for `grace_hours` (24 by default) so a fleet can roll over without downtime. The plaintext `token` is returned ONCE. Cookie/JWT only — a PAK cannot rotate a PAK, for the same reason it cannot mint one.
|
|
929
|
+
|
|
930
|
+
- Params: `PlatformAuth.WorkspaceApiKeyRotateParams`
|
|
931
|
+
|
|
932
|
+
| Status | Error class | Codes | When |
|
|
933
|
+
|---|---|---|---|
|
|
934
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session, or a session that has been signed out (`SESSION_REVOKED`). |
|
|
935
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | Caller is a PAK (`pcft_live_*`), or cannot grant one or more actions in the bindings being carried across. |
|
|
936
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | PAK not found in this workspace. |
|
|
937
|
+
| 409 | `ConflictError` | `CONFLICT`, `PAK_REVOKED`, `PAK_EXPIRED` | The key is revoked (`PAK_REVOKED`), already expired (`PAK_EXPIRED`), or has no policy bindings to carry across. |
|
|
938
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
939
|
+
|
|
940
|
+
```ts
|
|
941
|
+
const rotatePak = await platform.workspaces.apiKeys.rotate("workspace", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6", { grace_hours: 24 });
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-workspaces-workspace-slug-api-keys-id-rotate)
|
|
945
|
+
|
|
946
|
+
### `platform.workspaces.apiKeys.update(workspaceSlug, id, params?, options?)`
|
|
947
|
+
|
|
948
|
+
`PATCH /v1/workspaces/{workspace_slug}/api-keys/{id}` → `PlatformAuth.PublicApiKey`
|
|
949
|
+
|
|
950
|
+
Update a PAK’s name or description (does not touch bindings).
|
|
951
|
+
|
|
952
|
+
- Params: `PlatformAuth.WorkspaceApiKeyUpdateParams`
|
|
953
|
+
|
|
954
|
+
| Status | Error class | Codes | When |
|
|
955
|
+
|---|---|---|---|
|
|
956
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR`, `PAK_NAME_REQUIRED` | `id` is not a UUID, body failed validation, or `name` is blank (`PAK_NAME_REQUIRED`). |
|
|
957
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing, malformed or invalid session, or a PAK bearer (this route accepts cookie/JWT sessions only). Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
958
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.apikey.update`. |
|
|
959
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `PAK_NOT_FOUND` | Workspace not found, or PAK not found in this workspace (`PAK_NOT_FOUND`). |
|
|
960
|
+
| 409 | `ConflictError` | `CONFLICT`, `PAK_REVOKED` | PAK is revoked (`PAK_REVOKED`). |
|
|
961
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
962
|
+
|
|
963
|
+
```ts
|
|
964
|
+
const publicApiKey = await platform.workspaces.apiKeys.update("workspace", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6", { description: "string" });
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#patch-v1-workspaces-workspace-slug-api-keys-id)
|
|
968
|
+
|
|
969
|
+
### `platform.workspaces.apiKeys.updateBindings(workspaceSlug, id, params, options?)`
|
|
970
|
+
|
|
971
|
+
`PATCH /v1/workspaces/{workspace_slug}/api-keys/{id}/bindings` → `PlatformAuth.PublicApiKey`
|
|
972
|
+
|
|
973
|
+
Replace the managed-policy bindings on a PAK. Re-runs caller-narrowing on the merged union of statements.
|
|
974
|
+
|
|
975
|
+
- Params: `PlatformAuth.WorkspaceApiKeyUpdateBindingsParams`
|
|
976
|
+
|
|
977
|
+
| Status | Error class | Codes | When |
|
|
978
|
+
|---|---|---|---|
|
|
979
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | One of: bound > 25 policies; merged > 200 statements; one of the policy ids does not exist or lives in a different workspace. |
|
|
980
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session, or a session that has been signed out (`SESSION_REVOKED`). |
|
|
981
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | Caller cannot grant one or more actions in the merged statement set. |
|
|
982
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | PAK not found in this workspace. |
|
|
983
|
+
| 409 | `ConflictError` | `CONFLICT` | Empty bindings — revoke the PAK instead of leaving it unbound. |
|
|
984
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
985
|
+
|
|
986
|
+
```ts
|
|
987
|
+
const publicApiKey = await platform.workspaces.apiKeys.updateBindings("workspace", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6", { policy_ids: ["string"] });
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#patch-v1-workspaces-workspace-slug-api-keys-id-bindings)
|
|
991
|
+
|
|
992
|
+
## workspaces.apiKeys.presets
|
|
993
|
+
|
|
994
|
+
### `platform.workspaces.apiKeys.presets.list(workspaceSlug, options?)`
|
|
995
|
+
|
|
996
|
+
`GET /v1/workspaces/{workspace_slug}/api-keys/presets` → `PagePromise<PlatformAuth.PakPreset>`
|
|
997
|
+
|
|
998
|
+
List the ready-made permission sets a workspace key can be minted with (`preset` on create), each with a label, a description and what it allows in this workspace, in plain sentences. A preset a key has already used reads its stored policy, so edits show.
|
|
999
|
+
|
|
1000
|
+
- Paginated: `for await` iterates every item; `await` gives one page
|
|
1001
|
+
|
|
1002
|
+
| Status | Error class | Codes | When |
|
|
1003
|
+
|---|---|---|---|
|
|
1004
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, malformed or invalid session. |
|
|
1005
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.apikey.read`. |
|
|
1006
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found. |
|
|
1007
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1008
|
+
|
|
1009
|
+
```ts
|
|
1010
|
+
for await (const preset of platform.workspaces.apiKeys.presets.list("workspace")) {
|
|
1011
|
+
console.log(preset);
|
|
1012
|
+
}
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-api-keys-presets)
|
|
1016
|
+
|
|
1017
|
+
## workspaces.auditFeed
|
|
1018
|
+
|
|
1019
|
+
### `platform.workspaces.auditFeed.list(workspaceSlug, params?, options?)`
|
|
1020
|
+
|
|
1021
|
+
`GET /v1/workspaces/{workspace_slug}/audit-feed` → `PagePromise<PlatformAuth.FeedItem>`
|
|
1022
|
+
|
|
1023
|
+
Unified workspace audit feed (cursor-paginated).
|
|
1024
|
+
|
|
1025
|
+
Audit timeline for workspace-scoped events — workspace, member, role, invite, service-activation, PAK and OAuth-consent mutations, plus successful sign-ins and completed password resets. Per-app Auth rows are NOT merged in: they live in another database with no cursor that spans both, and they are served by `GET /v1/apps/:appId/audit-logs` (paginated), `.../audit-logs/export` (streamed) and the per-app audit sink. The `sources` field reports that explicitly rather than implying a pending follow-up.
|
|
1026
|
+
|
|
1027
|
+
- Params: `PlatformAuth.WorkspaceAuditFeedListParams`
|
|
1028
|
+
- Paginated: `for await` iterates every item; `await` gives one page
|
|
1029
|
+
|
|
1030
|
+
| Status | Error class | Codes | When |
|
|
1031
|
+
|---|---|---|---|
|
|
1032
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Invalid query params — `actor_id` is not a well-formed UUID, or `cursor` is not a cursor this endpoint minted. `since`/`until` that do not parse as ISO-8601 are ignored rather than rejected, and `sources` silently drops unknown names; an empty result set after those filters still returns 200. |
|
|
1033
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
1034
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | The caller is signed in but lacks `workspace.audit.read` for this workspace. |
|
|
1035
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | The `:workspaceSlug` path param does not resolve to a workspace the caller can see. |
|
|
1036
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1037
|
+
|
|
1038
|
+
```ts
|
|
1039
|
+
for await (const auditFeed of platform.workspaces.auditFeed.list("workspace")) {
|
|
1040
|
+
console.log(auditFeed);
|
|
1041
|
+
}
|
|
1042
|
+
const page = await platform.workspaces.auditFeed.list("workspace", {}, { timeout: 5_000 });
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-audit-feed)
|
|
1046
|
+
|
|
1047
|
+
## workspaces.auditLogs
|
|
1048
|
+
|
|
1049
|
+
### `platform.workspaces.auditLogs.export(workspaceSlug, params?, options?)`
|
|
1050
|
+
|
|
1051
|
+
`GET /v1/workspaces/{workspace_slug}/audit-logs/export` → `Response`
|
|
1052
|
+
|
|
1053
|
+
Stream the workspace audit log as NDJSON or CSV, applying the same `?action=` / `?actor_id=` / `?since=` / `?until=` filters as the list. Newest first. Reading the export is itself audited.
|
|
1054
|
+
|
|
1055
|
+
The response is streamed: rows are walked in keyset batches and written as they are read, so neither the service nor the client holds the whole log. This is the SOC 2 / GDPR export the platform docs promise. Hard cap of 1,000,000 rows per call; a wider request is refused with 413 before any body is written, so a truncated file can never masquerade as a complete one.
|
|
1056
|
+
|
|
1057
|
+
- Params: `PlatformAuth.WorkspaceAuditLogExportParams`
|
|
1058
|
+
|
|
1059
|
+
| Status | Error class | Codes | When |
|
|
1060
|
+
|---|---|---|---|
|
|
1061
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Unknown `format`, or malformed `since` / `until`. |
|
|
1062
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
1063
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | Caller lacks `workspace.audit.read`. |
|
|
1064
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found. |
|
|
1065
|
+
| 413 | `APIError` | `PAYLOAD_TOO_LARGE` | More than 1,000,000 rows match. Narrow the window with `since` / `until` and retry. |
|
|
1066
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1067
|
+
|
|
1068
|
+
```ts
|
|
1069
|
+
const response = await platform.workspaces.auditLogs.export("workspace");
|
|
1070
|
+
const text = await response.text();
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-audit-logs-export)
|
|
1074
|
+
|
|
1075
|
+
### `platform.workspaces.auditLogs.list(workspaceSlug, params?, options?)`
|
|
1076
|
+
|
|
1077
|
+
`GET /v1/workspaces/{workspace_slug}/audit-logs` → `PagePromise<PlatformAuth.WorkspaceAuditLogEntry>`
|
|
1078
|
+
|
|
1079
|
+
List workspace audit log entries, newest first. Filter by `?action=`, `?actor_id=`, and/or the `?since=` / `?until=` date range. 20/page (max 100), cursor-paginated.
|
|
1080
|
+
|
|
1081
|
+
- Params: `PlatformAuth.WorkspaceAuditLogListParams`
|
|
1082
|
+
- Paginated: `for await` iterates every item; `await` gives one page
|
|
1083
|
+
|
|
1084
|
+
| Status | Error class | Codes | When |
|
|
1085
|
+
|---|---|---|---|
|
|
1086
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Malformed `cursor`, `since`, or `until`. |
|
|
1087
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
1088
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1089
|
+
|
|
1090
|
+
```ts
|
|
1091
|
+
for await (const auditLog of platform.workspaces.auditLogs.list("workspace")) {
|
|
1092
|
+
console.log(auditLog);
|
|
1093
|
+
}
|
|
1094
|
+
const page = await platform.workspaces.auditLogs.list("workspace", {}, { timeout: 5_000 });
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-audit-logs)
|
|
1098
|
+
|
|
1099
|
+
## workspaces.avatar
|
|
1100
|
+
|
|
1101
|
+
### `platform.workspaces.avatar.clear(workspaceSlug, options?)`
|
|
1102
|
+
|
|
1103
|
+
`DELETE /v1/workspaces/{workspace_slug}/avatar` → `void`
|
|
1104
|
+
|
|
1105
|
+
Clear the workspace avatar and delete the stored object. Gated by `workspace.settings.update`.
|
|
1106
|
+
|
|
1107
|
+
| Status | Error class | Codes | When |
|
|
1108
|
+
|---|---|---|---|
|
|
1109
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing or invalid session. |
|
|
1110
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.settings.update`. |
|
|
1111
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found (unknown slug or id). |
|
|
1112
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1113
|
+
|
|
1114
|
+
```ts
|
|
1115
|
+
await platform.workspaces.avatar.clear("workspace");
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-workspaces-workspace-slug-avatar)
|
|
1119
|
+
|
|
1120
|
+
### `platform.workspaces.avatar.upload(workspaceSlug, params, options?)`
|
|
1121
|
+
|
|
1122
|
+
`POST /v1/workspaces/{workspace_slug}/avatar` → `PlatformAuth.WorkspaceAvatarUrl`
|
|
1123
|
+
|
|
1124
|
+
Upload (or replace) the workspace identity avatar. Gated by `workspace.settings.update` (owner + admin). PNG/JPEG/WEBP only, ≤2 MB, magic-byte sniffed. SVG is rejected (stored-XSS).
|
|
1125
|
+
|
|
1126
|
+
- Params: `PlatformAuth.WorkspaceAvatarUploadParams`
|
|
1127
|
+
|
|
1128
|
+
| Status | Error class | Codes | When |
|
|
1129
|
+
|---|---|---|---|
|
|
1130
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
1131
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN` | Caller is not a member of this workspace, or lacks workspace.settings.update. |
|
|
1132
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1133
|
+
|
|
1134
|
+
```ts
|
|
1135
|
+
const workspaceAvatarUrl = await platform.workspaces.avatar.upload("workspace", { file: new File(["…"], "image.png", { type: "image/png" }) });
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-workspaces-workspace-slug-avatar)
|
|
1139
|
+
|
|
1140
|
+
## workspaces.invites
|
|
1141
|
+
|
|
1142
|
+
### `platform.workspaces.invites.accept(params, options?)`
|
|
1143
|
+
|
|
1144
|
+
`POST /v1/workspaces/invites/accept` → `PlatformAuth.Workspace`
|
|
1145
|
+
|
|
1146
|
+
Accept a workspace invite by code. Cookie/JWT only — invite acceptance binds a human identity to a workspace.
|
|
1147
|
+
|
|
1148
|
+
- Params: `PlatformAuth.WorkspaceInviteAcceptParams`
|
|
1149
|
+
|
|
1150
|
+
| Status | Error class | Codes | When |
|
|
1151
|
+
|---|---|---|---|
|
|
1152
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Body failed validation (`code` missing or empty). |
|
|
1153
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session. Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
1154
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `EMAIL_NOT_VERIFIED`, `INVITE_EMAIL_MISMATCH` | Caller is a PAK (`pcft_live_*`), email not verified (`EMAIL_NOT_VERIFIED`), or the invite is restricted to a different email (`INVITE_EMAIL_MISMATCH`). |
|
|
1155
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `INVITE_INVALID` | Invite code not found (`INVITE_INVALID`). |
|
|
1156
|
+
| 409 | `ConflictError` | `CONFLICT`, `ALREADY_A_MEMBER` | Caller is already a member of this workspace (`ALREADY_A_MEMBER`). |
|
|
1157
|
+
| 410 | `APIError` | `GONE`, `INVITE_REVOKED`, `INVITE_EXPIRED`, `INVITE_EXHAUSTED` | Invite revoked (`INVITE_REVOKED`), expired (`INVITE_EXPIRED`) or out of uses (`INVITE_EXHAUSTED`). |
|
|
1158
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: more than 10 requests/min from this IP (strict `auth` bucket). `Retry-After` header set. Per-IP limit: rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1159
|
+
|
|
1160
|
+
```ts
|
|
1161
|
+
const workspace = await platform.workspaces.invites.accept({ code: "inv_…" });
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-workspaces-invites-accept)
|
|
1165
|
+
|
|
1166
|
+
### `platform.workspaces.invites.create(workspaceSlug, params?, options?)`
|
|
1167
|
+
|
|
1168
|
+
`POST /v1/workspaces/{workspace_slug}/invites` → `PlatformAuth.WorkspaceInvite`
|
|
1169
|
+
|
|
1170
|
+
Create a workspace invite link. Cookie/JWT only — invites bring new humans into the workspace.
|
|
1171
|
+
|
|
1172
|
+
- Params: `PlatformAuth.WorkspaceInviteCreateParams`
|
|
1173
|
+
- Idempotent: an `Idempotency-Key` is sent automatically and reused on retries
|
|
1174
|
+
|
|
1175
|
+
| Status | Error class | Codes | When |
|
|
1176
|
+
|---|---|---|---|
|
|
1177
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Body failed validation. |
|
|
1178
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session. Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
1179
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `EMAIL_NOT_VERIFIED`, `NOT_A_MEMBER`, `OWNER_ROLE_REQUIRED`, `MEMBER_RANK_EXCEEDED` | Caller is a PAK (`pcft_live_*`), email not verified (`EMAIL_NOT_VERIFIED`), not a member (`NOT_A_MEMBER`), lacks `workspace.member.invite`, a non-owner tried to invite an owner (`OWNER_ROLE_REQUIRED`), or the role holds permissions the caller does not (`MEMBER_RANK_EXCEEDED`). |
|
|
1180
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `ROLE_NOT_FOUND` | Workspace not found, or role not found in this workspace (`ROLE_NOT_FOUND`). |
|
|
1181
|
+
| 409 | `ConflictError` | `CONFLICT`, `IDEMPOTENCY_KEY_REUSE`, `IDEMPOTENCY_IN_PROGRESS` | Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key). |
|
|
1182
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1183
|
+
|
|
1184
|
+
```ts
|
|
1185
|
+
const workspaceInvite = await platform.workspaces.invites.create("workspace", { email: "alice@example.com" });
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-workspaces-workspace-slug-invites)
|
|
1189
|
+
|
|
1190
|
+
### `platform.workspaces.invites.delete(workspaceSlug, inviteId, options?)`
|
|
1191
|
+
|
|
1192
|
+
`DELETE /v1/workspaces/{workspace_slug}/invites/{invite_id}` → `void`
|
|
1193
|
+
|
|
1194
|
+
Revoke a workspace invite. Cookie/JWT only.
|
|
1195
|
+
|
|
1196
|
+
| Status | Error class | Codes | When |
|
|
1197
|
+
|---|---|---|---|
|
|
1198
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session. Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
1199
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is a PAK (`pcft_live_*`), not a member of the invite’s workspace (`NOT_A_MEMBER`), or lacks `workspace.member.invite`. |
|
|
1200
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `INVITE_NOT_FOUND` | Workspace not found, or invite not found (`INVITE_NOT_FOUND`). |
|
|
1201
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1202
|
+
|
|
1203
|
+
```ts
|
|
1204
|
+
await platform.workspaces.invites.delete("workspace", "invite");
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-workspaces-workspace-slug-invites-invite-id)
|
|
1208
|
+
|
|
1209
|
+
### `platform.workspaces.invites.list(workspaceSlug, params?, options?)`
|
|
1210
|
+
|
|
1211
|
+
`GET /v1/workspaces/{workspace_slug}/invites` → `PagePromise<PlatformAuth.WorkspaceInviteListItem>`
|
|
1212
|
+
|
|
1213
|
+
List workspace invites, newest first. Cursor-paginated.
|
|
1214
|
+
|
|
1215
|
+
- Params: `PlatformAuth.WorkspaceInviteListParams`
|
|
1216
|
+
- Paginated: `for await` iterates every item; `await` gives one page
|
|
1217
|
+
|
|
1218
|
+
| Status | Error class | Codes | When |
|
|
1219
|
+
|---|---|---|---|
|
|
1220
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Malformed `cursor`. |
|
|
1221
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing or invalid session. |
|
|
1222
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.member.invite`. |
|
|
1223
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found (unknown slug or id). |
|
|
1224
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1225
|
+
|
|
1226
|
+
```ts
|
|
1227
|
+
for await (const invite of platform.workspaces.invites.list("workspace")) {
|
|
1228
|
+
console.log(invite);
|
|
1229
|
+
}
|
|
1230
|
+
const page = await platform.workspaces.invites.list("workspace", {}, { timeout: 5_000 });
|
|
1231
|
+
```
|
|
1232
|
+
|
|
1233
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-invites)
|
|
1234
|
+
|
|
1235
|
+
## workspaces.members
|
|
1236
|
+
|
|
1237
|
+
### `platform.workspaces.members.delete(workspaceSlug, accountId, options?)`
|
|
1238
|
+
|
|
1239
|
+
`DELETE /v1/workspaces/{workspace_slug}/members/{account_id}` → `void`
|
|
1240
|
+
|
|
1241
|
+
Remove a member from a workspace (or self-leave).
|
|
1242
|
+
|
|
1243
|
+
| Status | Error class | Codes | When |
|
|
1244
|
+
|---|---|---|---|
|
|
1245
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session. Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
1246
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER`, `MEMBER_RANK_EXCEEDED` | Caller is not a member of this workspace (`NOT_A_MEMBER`), lacks `workspace.member.remove` (not required for self-leave), or the target outranks the caller (`MEMBER_RANK_EXCEEDED`: only an owner can remove an owner, and the target role's permissions must all be held by the caller). |
|
|
1247
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `NOT_A_MEMBER` | Workspace not found, or target account is not a member (`NOT_A_MEMBER`). |
|
|
1248
|
+
| 409 | `ConflictError` | `CONFLICT`, `LAST_MEMBER`, `SOLE_OWNER` | Target is the last member (`LAST_MEMBER`) or the sole owner (`SOLE_OWNER`). |
|
|
1249
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1250
|
+
|
|
1251
|
+
```ts
|
|
1252
|
+
await platform.workspaces.members.delete("workspace", "account");
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1255
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-workspaces-workspace-slug-members-account-id)
|
|
1256
|
+
|
|
1257
|
+
### `platform.workspaces.members.list(workspaceSlug, params?, options?)`
|
|
1258
|
+
|
|
1259
|
+
`GET /v1/workspaces/{workspace_slug}/members` → `PagePromise<PlatformAuth.WorkspaceMember>`
|
|
1260
|
+
|
|
1261
|
+
List workspace members, oldest join first. Narrow with `?q=` (name / username / email substring) and/or `?role=`. Cursor-paginated.
|
|
1262
|
+
|
|
1263
|
+
- Params: `PlatformAuth.WorkspaceMemberListParams`
|
|
1264
|
+
- Paginated: `for await` iterates every item; `await` gives one page
|
|
1265
|
+
|
|
1266
|
+
| Status | Error class | Codes | When |
|
|
1267
|
+
|---|---|---|---|
|
|
1268
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Malformed `cursor`. |
|
|
1269
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing or invalid session. |
|
|
1270
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.member.read`. |
|
|
1271
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found (unknown slug or id). |
|
|
1272
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1273
|
+
|
|
1274
|
+
```ts
|
|
1275
|
+
for await (const member of platform.workspaces.members.list("workspace")) {
|
|
1276
|
+
console.log(member);
|
|
1277
|
+
}
|
|
1278
|
+
const page = await platform.workspaces.members.list("workspace", {}, { timeout: 5_000 });
|
|
1279
|
+
```
|
|
1280
|
+
|
|
1281
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-members)
|
|
1282
|
+
|
|
1283
|
+
### `platform.workspaces.members.updateRole(workspaceSlug, accountId, params?, options?)`
|
|
1284
|
+
|
|
1285
|
+
`PATCH /v1/workspaces/{workspace_slug}/members/{account_id}/role` → `PlatformAuth.UpdateMemberRole`
|
|
1286
|
+
|
|
1287
|
+
Update a member's role. Body: { roleId } or { role: "owner"|"admin"|"member" }.
|
|
1288
|
+
|
|
1289
|
+
- Params: `PlatformAuth.WorkspaceMemberUpdateRoleParams`
|
|
1290
|
+
|
|
1291
|
+
| Status | Error class | Codes | When |
|
|
1292
|
+
|---|---|---|---|
|
|
1293
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Body failed validation. |
|
|
1294
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session. Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
1295
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER`, `OWNER_ROLE_REQUIRED`, `MEMBER_RANK_EXCEEDED` | Caller is not a member of this workspace (`NOT_A_MEMBER`), lacks `workspace.member.update-role`, a non-owner tried to assign the owner role or change an owner (`OWNER_ROLE_REQUIRED`), or a non-owner tried to change a role to or from one holding permissions the caller lacks (`MEMBER_RANK_EXCEEDED`). Admins may make admins, never owners. |
|
|
1296
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `NOT_A_MEMBER`, `ROLE_NOT_FOUND` | Workspace not found, target account is not a member (`NOT_A_MEMBER`), or role not found in this workspace (`ROLE_NOT_FOUND`). |
|
|
1297
|
+
| 409 | `ConflictError` | `CONFLICT`, `SOLE_OWNER` | Would demote the only owner (`SOLE_OWNER`). Assign another owner first. |
|
|
1298
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1299
|
+
|
|
1300
|
+
```ts
|
|
1301
|
+
const updateMemberRole = await platform.workspaces.members.updateRole("workspace", "account", { role: "owner" });
|
|
1302
|
+
```
|
|
1303
|
+
|
|
1304
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#patch-v1-workspaces-workspace-slug-members-account-id-role)
|
|
1305
|
+
|
|
1306
|
+
## workspaces.policies
|
|
1307
|
+
|
|
1308
|
+
### `platform.workspaces.policies.create(workspaceSlug, params, options?)`
|
|
1309
|
+
|
|
1310
|
+
`POST /v1/workspaces/{workspace_slug}/policies` → `PlatformAuth.ManagedPolicy`
|
|
1311
|
+
|
|
1312
|
+
Create a managed policy. Cookie/JWT only — policy authoring is a human-admin surface.
|
|
1313
|
+
|
|
1314
|
+
- Params: `PlatformAuth.WorkspacePolicyCreateParams`
|
|
1315
|
+
- Idempotent: an `Idempotency-Key` is sent automatically and reused on retries
|
|
1316
|
+
|
|
1317
|
+
| Status | Error class | Codes | When |
|
|
1318
|
+
|---|---|---|---|
|
|
1319
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Invalid policy shape, name, or condition block. |
|
|
1320
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session, or a session that has been signed out (`SESSION_REVOKED`). |
|
|
1321
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member (`NOT_A_MEMBER`), lacks `workspace.policy.create` (owner and admin hold it by default), or the policy grants an action the caller does not hold. |
|
|
1322
|
+
| 409 | `ConflictError` | `CONFLICT`, `IDEMPOTENCY_KEY_REUSE`, `IDEMPOTENCY_IN_PROGRESS` | Name already in use, or the workspace already has 100 policies. Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key). |
|
|
1323
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1324
|
+
|
|
1325
|
+
```ts
|
|
1326
|
+
const managedPolicy = await platform.workspaces.policies.create("workspace", {
|
|
1327
|
+
name: "mail-readonly",
|
|
1328
|
+
policy: [{ actions: ["mail.read", "mail.list"], effect: "allow", resources: ["*"] }],
|
|
1329
|
+
});
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-workspaces-workspace-slug-policies)
|
|
1333
|
+
|
|
1334
|
+
### `platform.workspaces.policies.delete(workspaceSlug, policyId, options?)`
|
|
1335
|
+
|
|
1336
|
+
`DELETE /v1/workspaces/{workspace_slug}/policies/{policy_id}` → `PlatformAuth.ManagedPolicyDeleted`
|
|
1337
|
+
|
|
1338
|
+
Delete a managed policy. Cascades: API keys lose the binding (deny-all if no other bindings remain); roles fall back to their inline policy column. Owners and admins; a policy bound to a role, or a preset policy, only an owner can delete. Cookie/JWT only.
|
|
1339
|
+
|
|
1340
|
+
| Status | Error class | Codes | When |
|
|
1341
|
+
|---|---|---|---|
|
|
1342
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session, or a session that has been signed out (`SESSION_REVOKED`). |
|
|
1343
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `OWNER_ROLE_REQUIRED` | Caller lacks `workspace.policy.delete`, or is not an owner and the policy is bound to a role or is a preset policy (`OWNER_ROLE_REQUIRED`). |
|
|
1344
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `POLICY_NOT_FOUND` | Policy not found in this workspace (`POLICY_NOT_FOUND`). |
|
|
1345
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1346
|
+
|
|
1347
|
+
```ts
|
|
1348
|
+
const managedPolicyDeleted = await platform.workspaces.policies.delete("workspace", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6");
|
|
1349
|
+
```
|
|
1350
|
+
|
|
1351
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-workspaces-workspace-slug-policies-policy-id)
|
|
1352
|
+
|
|
1353
|
+
### `platform.workspaces.policies.list(workspaceSlug, params?, options?)`
|
|
1354
|
+
|
|
1355
|
+
`GET /v1/workspaces/{workspace_slug}/policies` → `PagePromise<PlatformAuth.ManagedPolicy>`
|
|
1356
|
+
|
|
1357
|
+
List managed policies in a workspace, alphabetically. 20/page (max 100), cursor-paginated.
|
|
1358
|
+
|
|
1359
|
+
- Params: `PlatformAuth.WorkspacePolicyListParams`
|
|
1360
|
+
- Paginated: `for await` iterates every item; `await` gives one page
|
|
1361
|
+
|
|
1362
|
+
| Status | Error class | Codes | When |
|
|
1363
|
+
|---|---|---|---|
|
|
1364
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Malformed `cursor`. |
|
|
1365
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
1366
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1367
|
+
|
|
1368
|
+
```ts
|
|
1369
|
+
for await (const policy of platform.workspaces.policies.list("workspace")) {
|
|
1370
|
+
console.log(policy);
|
|
1371
|
+
}
|
|
1372
|
+
const page = await platform.workspaces.policies.list("workspace", {}, { timeout: 5_000 });
|
|
1373
|
+
```
|
|
1374
|
+
|
|
1375
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-policies)
|
|
1376
|
+
|
|
1377
|
+
### `platform.workspaces.policies.retrieve(workspaceSlug, policyId, options?)`
|
|
1378
|
+
|
|
1379
|
+
`GET /v1/workspaces/{workspace_slug}/policies/{policy_id}` → `PlatformAuth.ManagedPolicyDetail`
|
|
1380
|
+
|
|
1381
|
+
Get a managed policy by id.
|
|
1382
|
+
|
|
1383
|
+
| Status | Error class | Codes | When |
|
|
1384
|
+
|---|---|---|---|
|
|
1385
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
1386
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Policy not found in this workspace. |
|
|
1387
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1388
|
+
|
|
1389
|
+
```ts
|
|
1390
|
+
const managedPolicyDetail = await platform.workspaces.policies.retrieve("workspace", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6");
|
|
1391
|
+
```
|
|
1392
|
+
|
|
1393
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-policies-policy-id)
|
|
1394
|
+
|
|
1395
|
+
### `platform.workspaces.policies.update(workspaceSlug, policyId, params?, options?)`
|
|
1396
|
+
|
|
1397
|
+
`PATCH /v1/workspaces/{workspace_slug}/policies/{policy_id}` → `PlatformAuth.ManagedPolicyDetail`
|
|
1398
|
+
|
|
1399
|
+
Update a managed policy. Owners and admins; a policy bound to a role, or a preset policy, only an owner can edit. Cookie/JWT only — policy authoring is a human-admin surface.
|
|
1400
|
+
|
|
1401
|
+
- Params: `PlatformAuth.WorkspacePolicyUpdateParams`
|
|
1402
|
+
|
|
1403
|
+
| Status | Error class | Codes | When |
|
|
1404
|
+
|---|---|---|---|
|
|
1405
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Invalid policy shape, name, or condition block. |
|
|
1406
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing or invalid session, or a session that has been signed out (`SESSION_REVOKED`). |
|
|
1407
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `OWNER_ROLE_REQUIRED` | Caller lacks `workspace.policy.update`, cannot grant one or more requested actions, or is not an owner and the policy is bound to a role or is a preset policy (`OWNER_ROLE_REQUIRED`). |
|
|
1408
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `POLICY_NOT_FOUND` | Policy not found in this workspace (`POLICY_NOT_FOUND`). |
|
|
1409
|
+
| 409 | `ConflictError` | `CONFLICT`, `POLICY_NAME_TAKEN` | Name already in use (`POLICY_NAME_TAKEN`). |
|
|
1410
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1411
|
+
|
|
1412
|
+
```ts
|
|
1413
|
+
const managedPolicyDetail = await platform.workspaces.policies.update("workspace", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6", { description: "Read-only access to all Mail resources" });
|
|
1414
|
+
```
|
|
1415
|
+
|
|
1416
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#patch-v1-workspaces-workspace-slug-policies-policy-id)
|
|
1417
|
+
|
|
1418
|
+
## workspaces.policies.actions
|
|
1419
|
+
|
|
1420
|
+
### `platform.workspaces.policies.actions.catalog(workspaceSlug, options?)`
|
|
1421
|
+
|
|
1422
|
+
`GET /v1/workspaces/{workspace_slug}/policies/actions/catalog` → `PlatformAuth.WorkspaceActionCatalog`
|
|
1423
|
+
|
|
1424
|
+
List every known workspace action (catalog of statement actions).
|
|
1425
|
+
|
|
1426
|
+
| Status | Error class | Codes | When |
|
|
1427
|
+
|---|---|---|---|
|
|
1428
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, malformed or invalid session, or a PAK bearer (this route accepts cookie/JWT sessions only). |
|
|
1429
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.policy.read`. |
|
|
1430
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found (unknown slug or id). |
|
|
1431
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1432
|
+
|
|
1433
|
+
```ts
|
|
1434
|
+
const workspaceActionCatalog = await platform.workspaces.policies.actions.catalog("workspace");
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1437
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-policies-actions-catalog)
|
|
1438
|
+
|
|
1439
|
+
## workspaces.roles
|
|
1440
|
+
|
|
1441
|
+
### `platform.workspaces.roles.create(workspaceSlug, params, options?)`
|
|
1442
|
+
|
|
1443
|
+
`POST /v1/workspaces/{workspace_slug}/roles` → `PlatformAuth.WorkspaceRole`
|
|
1444
|
+
|
|
1445
|
+
Create a new custom role. Cookie/JWT only — role authoring is a human-admin surface.
|
|
1446
|
+
|
|
1447
|
+
- Params: `PlatformAuth.WorkspaceRoleCreateParams`
|
|
1448
|
+
- Idempotent: an `Idempotency-Key` is sent automatically and reused on retries
|
|
1449
|
+
|
|
1450
|
+
| Status | Error class | Codes | When |
|
|
1451
|
+
|---|---|---|---|
|
|
1452
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR`, `POLICY_INVALID` | Body failed validation, invalid role name, malformed policy, both `policy_id` and `policy` sent, or an action pattern matches no known permission (`POLICY_INVALID`). |
|
|
1453
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing, malformed or invalid session, or a PAK bearer (this route accepts cookie/JWT sessions only). Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
1454
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER`, `ACTIONS_NOT_GRANTABLE` | Caller is not a member of this workspace (`NOT_A_MEMBER`), lacks `workspace.role.create`, or the policy grants actions the caller does not hold (`ACTIONS_NOT_GRANTABLE`). |
|
|
1455
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `POLICY_NOT_FOUND` | Workspace not found, or `policy_id` not found in this workspace (`POLICY_NOT_FOUND`). |
|
|
1456
|
+
| 409 | `ConflictError` | `CONFLICT`, `ROLE_NAME_TAKEN`, `IDEMPOTENCY_KEY_REUSE`, `IDEMPOTENCY_IN_PROGRESS` | Role name already in use or reserved for a system role (`ROLE_NAME_TAKEN`). Idempotency: `IDEMPOTENCY_KEY_REUSE` when this `Idempotency-Key` was already used with a different body or route; `IDEMPOTENCY_IN_PROGRESS` while another request with the same key is still running (retry after `Retry-After` seconds, with the same key). |
|
|
1457
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1458
|
+
|
|
1459
|
+
```ts
|
|
1460
|
+
const workspaceRole = await platform.workspaces.roles.create("workspace", { name: "BillingAdmin" });
|
|
1461
|
+
```
|
|
1462
|
+
|
|
1463
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-workspaces-workspace-slug-roles)
|
|
1464
|
+
|
|
1465
|
+
### `platform.workspaces.roles.delete(workspaceSlug, roleId, options?)`
|
|
1466
|
+
|
|
1467
|
+
`DELETE /v1/workspaces/{workspace_slug}/roles/{role_id}` → `void`
|
|
1468
|
+
|
|
1469
|
+
Delete a custom role (fails if it has members). Cookie/JWT only — role authoring is a human-admin surface.
|
|
1470
|
+
|
|
1471
|
+
| Status | Error class | Codes | When |
|
|
1472
|
+
|---|---|---|---|
|
|
1473
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | `roleId` is not a UUID. |
|
|
1474
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing, malformed or invalid session, or a PAK bearer (this route accepts cookie/JWT sessions only). Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
1475
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.role.delete`. |
|
|
1476
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `ROLE_NOT_FOUND` | Workspace not found, or role not found in this workspace (`ROLE_NOT_FOUND`). |
|
|
1477
|
+
| 409 | `ConflictError` | `CONFLICT`, `ROLE_SYSTEM_IMMUTABLE`, `ROLE_HAS_MEMBERS` | Role is a system role (`ROLE_SYSTEM_IMMUTABLE`) or still has members (`ROLE_HAS_MEMBERS`). |
|
|
1478
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1479
|
+
|
|
1480
|
+
```ts
|
|
1481
|
+
await platform.workspaces.roles.delete("workspace", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6");
|
|
1482
|
+
```
|
|
1483
|
+
|
|
1484
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-workspaces-workspace-slug-roles-role-id)
|
|
1485
|
+
|
|
1486
|
+
### `platform.workspaces.roles.list(workspaceSlug, params?, options?)`
|
|
1487
|
+
|
|
1488
|
+
`GET /v1/workspaces/{workspace_slug}/roles` → `PagePromise<PlatformAuth.WorkspaceRole>`
|
|
1489
|
+
|
|
1490
|
+
List roles in a workspace. System roles first, then alphabetical. 20/page (max 100), cursor-paginated.
|
|
1491
|
+
|
|
1492
|
+
- Params: `PlatformAuth.WorkspaceRoleListParams`
|
|
1493
|
+
- Paginated: `for await` iterates every item; `await` gives one page
|
|
1494
|
+
|
|
1495
|
+
| Status | Error class | Codes | When |
|
|
1496
|
+
|---|---|---|---|
|
|
1497
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | Malformed `cursor`. |
|
|
1498
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, invalid or expired bearer token. |
|
|
1499
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1500
|
+
|
|
1501
|
+
```ts
|
|
1502
|
+
for await (const role of platform.workspaces.roles.list("workspace")) {
|
|
1503
|
+
console.log(role);
|
|
1504
|
+
}
|
|
1505
|
+
const page = await platform.workspaces.roles.list("workspace", {}, { timeout: 5_000 });
|
|
1506
|
+
```
|
|
1507
|
+
|
|
1508
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-roles)
|
|
1509
|
+
|
|
1510
|
+
### `platform.workspaces.roles.retrieve(workspaceSlug, roleId, options?)`
|
|
1511
|
+
|
|
1512
|
+
`GET /v1/workspaces/{workspace_slug}/roles/{role_id}` → `PlatformAuth.WorkspaceRole`
|
|
1513
|
+
|
|
1514
|
+
Get a role + its permissions.
|
|
1515
|
+
|
|
1516
|
+
| Status | Error class | Codes | When |
|
|
1517
|
+
|---|---|---|---|
|
|
1518
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | `roleId` is not a UUID. |
|
|
1519
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, malformed or invalid session, or a PAK bearer (this route accepts cookie/JWT sessions only). |
|
|
1520
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.role.read`. |
|
|
1521
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `ROLE_NOT_FOUND` | Workspace not found, or role not found in this workspace (`ROLE_NOT_FOUND`). |
|
|
1522
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1523
|
+
|
|
1524
|
+
```ts
|
|
1525
|
+
const workspaceRole = await platform.workspaces.roles.retrieve("workspace", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6");
|
|
1526
|
+
```
|
|
1527
|
+
|
|
1528
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-roles-role-id)
|
|
1529
|
+
|
|
1530
|
+
### `platform.workspaces.roles.update(workspaceSlug, roleId, params?, options?)`
|
|
1531
|
+
|
|
1532
|
+
`PATCH /v1/workspaces/{workspace_slug}/roles/{role_id}` → `PlatformAuth.WorkspaceRole`
|
|
1533
|
+
|
|
1534
|
+
Update a role or replace its permission set. Cookie/JWT only — role authoring is a human-admin surface.
|
|
1535
|
+
|
|
1536
|
+
- Params: `PlatformAuth.WorkspaceRoleUpdateParams`
|
|
1537
|
+
|
|
1538
|
+
| Status | Error class | Codes | When |
|
|
1539
|
+
|---|---|---|---|
|
|
1540
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR` | `roleId` is not a UUID, body failed validation, malformed policy, both `policy_id` and `policy` sent, or an action pattern matches no known permission. |
|
|
1541
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED`, `SESSION_REVOKED` | Missing, malformed or invalid session, or a PAK bearer (this route accepts cookie/JWT sessions only). Also a session that has been signed out (`SESSION_REVOKED`). |
|
|
1542
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER`, `ACTIONS_NOT_GRANTABLE` | Caller is not a member of this workspace (`NOT_A_MEMBER`), lacks `workspace.role.update`, or the policy grants actions the caller does not hold (`ACTIONS_NOT_GRANTABLE`). |
|
|
1543
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `ROLE_NOT_FOUND`, `POLICY_NOT_FOUND` | Workspace, role (`ROLE_NOT_FOUND`) or bound policy (`POLICY_NOT_FOUND`) not found in this workspace. |
|
|
1544
|
+
| 409 | `ConflictError` | `CONFLICT`, `ROLE_SYSTEM_IMMUTABLE`, `ROLE_NAME_TAKEN` | System roles cannot be renamed, edited or bound (`ROLE_SYSTEM_IMMUTABLE`), or the new name is taken (`ROLE_NAME_TAKEN`). |
|
|
1545
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1546
|
+
|
|
1547
|
+
```ts
|
|
1548
|
+
const workspaceRole = await platform.workspaces.roles.update("workspace", "5d1e2f3a-4b5c-4d6e-8f70-8192a3b4c5d6", { description: "string" });
|
|
1549
|
+
```
|
|
1550
|
+
|
|
1551
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#patch-v1-workspaces-workspace-slug-roles-role-id)
|
|
1552
|
+
|
|
1553
|
+
## workspaces.services
|
|
1554
|
+
|
|
1555
|
+
### `platform.workspaces.services.disable(workspaceSlug, service, options?)`
|
|
1556
|
+
|
|
1557
|
+
`DELETE /v1/workspaces/{workspace_slug}/services/{service}` → `void`
|
|
1558
|
+
|
|
1559
|
+
Disable a service (settings are retained).
|
|
1560
|
+
|
|
1561
|
+
| Status | Error class | Codes | When |
|
|
1562
|
+
|---|---|---|---|
|
|
1563
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR`, `SERVICE_UNKNOWN` | Unknown service name (`SERVICE_UNKNOWN`). |
|
|
1564
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, malformed or invalid session, or a PAK bearer (this route accepts cookie/JWT sessions only). |
|
|
1565
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.service.disable`. |
|
|
1566
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found (unknown slug or id). |
|
|
1567
|
+
| 409 | `ConflictError` | `CONFLICT`, `SERVICE_NOT_ENABLED` | Service is not currently enabled (`SERVICE_NOT_ENABLED`). |
|
|
1568
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1569
|
+
|
|
1570
|
+
```ts
|
|
1571
|
+
await platform.workspaces.services.disable("workspace", "service");
|
|
1572
|
+
```
|
|
1573
|
+
|
|
1574
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#delete-v1-workspaces-workspace-slug-services-service)
|
|
1575
|
+
|
|
1576
|
+
### `platform.workspaces.services.enable(workspaceSlug, service, params?, options?)`
|
|
1577
|
+
|
|
1578
|
+
`POST /v1/workspaces/{workspace_slug}/services/{service}` → `PlatformAuth.WorkspaceServiceState`
|
|
1579
|
+
|
|
1580
|
+
Enable (or re-enable) a service for this workspace.
|
|
1581
|
+
|
|
1582
|
+
- Params: `PlatformAuth.WorkspaceServiceEnableParams`
|
|
1583
|
+
|
|
1584
|
+
| Status | Error class | Codes | When |
|
|
1585
|
+
|---|---|---|---|
|
|
1586
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR`, `SERVICE_UNKNOWN` | Unknown service name (`SERVICE_UNKNOWN`) or body failed validation. |
|
|
1587
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, malformed or invalid session, or a PAK bearer (this route accepts cookie/JWT sessions only). |
|
|
1588
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.service.enable`. |
|
|
1589
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found (unknown slug or id). |
|
|
1590
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1591
|
+
|
|
1592
|
+
```ts
|
|
1593
|
+
const workspaceServiceState = await platform.workspaces.services.enable("workspace", "service", { settings: {} });
|
|
1594
|
+
```
|
|
1595
|
+
|
|
1596
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#post-v1-workspaces-workspace-slug-services-service)
|
|
1597
|
+
|
|
1598
|
+
### `platform.workspaces.services.list(workspaceSlug, options?)`
|
|
1599
|
+
|
|
1600
|
+
`GET /v1/workspaces/{workspace_slug}/services` → `PlatformAuth.ListWorkspaceServices`
|
|
1601
|
+
|
|
1602
|
+
List services activated on this workspace. Complete — one row per product, not paginated.
|
|
1603
|
+
|
|
1604
|
+
| Status | Error class | Codes | When |
|
|
1605
|
+
|---|---|---|---|
|
|
1606
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, malformed or invalid session, or a PAK bearer (this route accepts cookie/JWT sessions only). |
|
|
1607
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.service.read`. |
|
|
1608
|
+
| 404 | `NotFoundError` | `NOT_FOUND` | Workspace not found (unknown slug or id). |
|
|
1609
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1610
|
+
|
|
1611
|
+
```ts
|
|
1612
|
+
const listWorkspaceServices = await platform.workspaces.services.list("workspace");
|
|
1613
|
+
```
|
|
1614
|
+
|
|
1615
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#get-v1-workspaces-workspace-slug-services)
|
|
1616
|
+
|
|
1617
|
+
### `platform.workspaces.services.updateSettings(workspaceSlug, service, params?, options?)`
|
|
1618
|
+
|
|
1619
|
+
`PATCH /v1/workspaces/{workspace_slug}/services/{service}/settings` → `PlatformAuth.WorkspaceServiceState`
|
|
1620
|
+
|
|
1621
|
+
Replace the settings jsonb for an active service.
|
|
1622
|
+
|
|
1623
|
+
- Params: `PlatformAuth.WorkspaceServiceUpdateSettingsParams`
|
|
1624
|
+
|
|
1625
|
+
| Status | Error class | Codes | When |
|
|
1626
|
+
|---|---|---|---|
|
|
1627
|
+
| 400 | `BadRequestError` | `BAD_REQUEST`, `VALIDATION_ERROR`, `SERVICE_UNKNOWN` | Unknown service name (`SERVICE_UNKNOWN`). |
|
|
1628
|
+
| 401 | `AuthenticationError` | `UNAUTHORIZED` | Missing, malformed or invalid session, or a PAK bearer (this route accepts cookie/JWT sessions only). |
|
|
1629
|
+
| 403 | `PermissionDeniedError` | `FORBIDDEN`, `NOT_A_MEMBER` | Caller is not a member of this workspace (`NOT_A_MEMBER`), or lacks `workspace.service.enable`. |
|
|
1630
|
+
| 404 | `NotFoundError` | `NOT_FOUND`, `SERVICE_NOT_ACTIVATED` | Workspace not found, or the service was never activated on it (`SERVICE_NOT_ACTIVATED`). |
|
|
1631
|
+
| 429 | `RateLimitError` | `RATE_LIMITED` | Rate limited: too many requests from this client IP in the last 60 seconds (`RATE_LIMITED`); wait `Retry-After` seconds. |
|
|
1632
|
+
|
|
1633
|
+
```ts
|
|
1634
|
+
const workspaceServiceState = await platform.workspaces.services.updateSettings("workspace", "service", { default_sender: "noreply@acme.com" });
|
|
1635
|
+
```
|
|
1636
|
+
|
|
1637
|
+
[HTTP reference](https://productcraft.co/docs/platform/api-reference#patch-v1-workspaces-workspace-slug-services-service-settings)
|
|
1638
|
+
|
|
1639
|
+
## Error codes
|
|
1640
|
+
|
|
1641
|
+
`err.code` on an `APIError` is one of these (an open set: new codes may appear).
|
|
1642
|
+
|
|
1643
|
+
| Code | Meaning |
|
|
1644
|
+
|---|---|
|
|
1645
|
+
| `BAD_REQUEST` | The request is malformed (not valid JSON, a bad header, an unusable parameter). |
|
|
1646
|
+
| `VALIDATION_ERROR` | One or more request fields are invalid; `errors` lists each field (`param`, `message`, `code`). |
|
|
1647
|
+
| `UNAUTHORIZED` | No credentials, or the token or API key is invalid, expired or revoked. |
|
|
1648
|
+
| `PAYMENT_REQUIRED` | The workspace has no credit or plan for this operation. |
|
|
1649
|
+
| `FORBIDDEN` | The caller is authenticated but not allowed to do this. |
|
|
1650
|
+
| `NOT_FOUND` | The resource does not exist, or belongs to another workspace or app. |
|
|
1651
|
+
| `METHOD_NOT_ALLOWED` | The route does not accept this HTTP method. |
|
|
1652
|
+
| `NOT_ACCEPTABLE` | The route cannot produce the requested content type. |
|
|
1653
|
+
| `REQUEST_TIMEOUT` | The request took too long to arrive. |
|
|
1654
|
+
| `CONFLICT` | The request conflicts with the current state of the resource. |
|
|
1655
|
+
| `GONE` | The resource existed but has been removed for good. |
|
|
1656
|
+
| `PRECONDITION_FAILED` | A precondition header did not match. |
|
|
1657
|
+
| `PAYLOAD_TOO_LARGE` | The request body is over the size limit. |
|
|
1658
|
+
| `UNSUPPORTED_MEDIA_TYPE` | The request body is not in a content type the route accepts. |
|
|
1659
|
+
| `UNPROCESSABLE_ENTITY` | The request is well-formed but cannot be carried out. |
|
|
1660
|
+
| `PRECONDITION_REQUIRED` | The route requires a precondition header. |
|
|
1661
|
+
| `RATE_LIMITED` | Too many requests; retry after the `Retry-After` header. |
|
|
1662
|
+
| `INTERNAL_SERVER_ERROR` | An unexpected server error. Retrying is safe for idempotent requests; quote `request_id` to support. |
|
|
1663
|
+
| `NOT_IMPLEMENTED` | The server does not support this operation. |
|
|
1664
|
+
| `BAD_GATEWAY` | An upstream service returned an invalid response. |
|
|
1665
|
+
| `SERVICE_UNAVAILABLE` | The service or one of its dependencies is temporarily unavailable; retry. |
|
|
1666
|
+
| `GATEWAY_TIMEOUT` | An upstream service did not answer in time; retry. |
|
|
1667
|
+
| `SERVICE_NOT_ENABLED` | The workspace has not enabled this product. |
|
|
1668
|
+
| `INVALID_CREDENTIALS` | The credentials were not accepted. |
|
|
1669
|
+
| `SESSION_REVOKED` | The session behind this token was signed out or revoked. |
|
|
1670
|
+
| `MCP_NOT_PERMITTED` | An MCP (agent) token cannot call this route. |
|
|
1671
|
+
| `MCP_WORKSPACE_MISMATCH` | The MCP token was issued for a different workspace. |
|
|
1672
|
+
| `PAK_NOT_PERMITTED` | A workspace API key cannot call this route. |
|
|
1673
|
+
| `ACTIONS_NOT_GRANTABLE` | The policy grants actions the caller does not hold. |
|
|
1674
|
+
| `IDEMPOTENCY_KEY_REUSE` | The `Idempotency-Key` was already used with a different body or route. |
|
|
1675
|
+
| `IDEMPOTENCY_KEY_REQUIRED` | This operation requires an `Idempotency-Key` header. |
|
|
1676
|
+
| `IDEMPOTENCY_IN_PROGRESS` | A request with the same `Idempotency-Key` is still running. Retry after `Retry-After` seconds with the same key to get its response. |
|
|
1677
|
+
| `WEBHOOK_ENDPOINT_LIMIT` | The workspace has the maximum number of webhook endpoints. |
|
|
1678
|
+
| `WEBHOOK_ENDPOINT_NOT_FOUND` | No webhook endpoint with this id in this workspace. |
|
|
1679
|
+
| `WEBHOOK_ENDPOINT_NOT_SUBSCRIBED` | The endpoint is not subscribed to this event type. |
|
|
1680
|
+
| `WEBHOOK_EVENT_NOT_FOUND` | No webhook event with this id in this workspace. |
|
|
1681
|
+
| `WEBHOOK_EVENT_TYPE_UNKNOWN` | The event type is not one this product sends. |
|
|
1682
|
+
| `ACCOUNT_DEACTIVATED` | Account deactivated. |
|
|
1683
|
+
| `ACCOUNT_SUSPENDED` | Account suspended. |
|
|
1684
|
+
| `ALREADY_A_MEMBER` | Already a member. |
|
|
1685
|
+
| `CROSS_ORIGIN_REJECTED` | The request came from an origin that is not allowed for this cookie-authenticated route. |
|
|
1686
|
+
| `EMAIL_NOT_VERIFIED` | Verify the account email first. |
|
|
1687
|
+
| `INVITE_EMAIL_MISMATCH` | The invite was sent to a different email address. |
|
|
1688
|
+
| `INVITE_EXHAUSTED` | Invite exhausted. |
|
|
1689
|
+
| `INVITE_EXPIRED` | Invite expired. |
|
|
1690
|
+
| `INVITE_INVALID` | Invite invalid. |
|
|
1691
|
+
| `INVITE_NOT_FOUND` | Invite not found. |
|
|
1692
|
+
| `INVITE_REVOKED` | Invite revoked. |
|
|
1693
|
+
| `LAST_MEMBER` | The workspace's last member cannot leave; delete the workspace instead. |
|
|
1694
|
+
| `MEMBER_RANK_EXCEEDED` | You cannot manage a member whose role ranks above yours. |
|
|
1695
|
+
| `NOT_A_MEMBER` | The caller is not a member of this workspace. |
|
|
1696
|
+
| `OAUTH_GRANT_ADMIN_REVOKED` | An admin revoked this OAuth grant. |
|
|
1697
|
+
| `OWNER_ROLE_REQUIRED` | Only an owner can do this. |
|
|
1698
|
+
| `PAK_ALREADY_MINTED` | This Idempotency-Key already minted an API key; its token is shown once. Rotate that key (its `id` is in `details`) for a new token. |
|
|
1699
|
+
| `PAK_EXPIRED` | Pak expired. |
|
|
1700
|
+
| `PAK_NAME_REQUIRED` | An API key needs a name. |
|
|
1701
|
+
| `PAK_NAME_TOO_LONG` | The API key name is too long. |
|
|
1702
|
+
| `PAK_NOT_FOUND` | Pak not found. |
|
|
1703
|
+
| `PAK_REVOKED` | Pak revoked. |
|
|
1704
|
+
| `PASSWORD_RESET_INVALID` | The password reset code is invalid or expired. |
|
|
1705
|
+
| `POLICY_INVALID` | The policy document is invalid. |
|
|
1706
|
+
| `POLICY_NAME_TAKEN` | Policy name taken. |
|
|
1707
|
+
| `POLICY_NOT_FOUND` | Policy not found. |
|
|
1708
|
+
| `ROLE_HAS_MEMBERS` | The role still has members; move them first. |
|
|
1709
|
+
| `ROLE_NAME_TAKEN` | Role name taken. |
|
|
1710
|
+
| `ROLE_NOT_FOUND` | Role not found. |
|
|
1711
|
+
| `ROLE_SYSTEM_IMMUTABLE` | System roles (owner, admin, member) cannot be changed or deleted. |
|
|
1712
|
+
| `SERVICE_NOT_ACTIVATED` | The product is not activated for this workspace. |
|
|
1713
|
+
| `SERVICE_UNKNOWN` | No product with this name. |
|
|
1714
|
+
| `SOLE_OWNER` | The workspace's only owner cannot leave or be removed; transfer ownership first. |
|
|
1715
|
+
| `TOO_MANY_ATTEMPTS` | Too many attempts; wait before trying again. |
|
|
1716
|
+
| `WORKSPACE_KEY_REQUIRED` | This route needs a workspace API key. |
|