@picked/client 0.0.62-next.1 → 0.0.62-next.11
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 +0 -8
- package/dist/{api-Dfrznen2.js → api-C6HQ65g9.js} +1 -1
- package/dist/api-v323-Asd.cjs +1 -0
- package/dist/cjs/form/index.js +1 -1
- package/dist/cjs/index.js +4 -4
- package/dist/cjs/plugins/create-experience/index.js +1 -1
- package/dist/cjs/plugins/dom-observer/index.js +1 -0
- package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
- package/dist/cjs/plugins/experience-browser-storage/index.js +1 -1
- package/dist/cjs/plugins/history-browser-storage/index.js +1 -1
- package/dist/cjs/plugins/responses-browser-storage/index.js +1 -1
- package/dist/cjs/plugins/single-page-app/index.js +1 -1
- package/dist/cjs/plugins/web-components/index.js +1 -1
- package/dist/cjs/server/index.js +1 -1
- package/dist/docs/actions.mdx +0 -1
- package/dist/docs/api/authentication.md +376 -0
- package/dist/docs/api/authorization-and-scopes.md +262 -0
- package/dist/docs/api/error-handling.md +211 -0
- package/dist/docs/api/index.md +172 -11
- package/dist/docs/api/pagination.md +176 -0
- package/dist/docs/image-optimisation-service.md +72 -0
- package/dist/docs/index.md +0 -2
- package/dist/docs/pickedql/data-sources.md +2 -2
- package/dist/docs/pickedql/dimensions.md +2 -0
- package/dist/docs/pickedql/schema.md +2 -2
- package/dist/docs/pickedql/virtual-functions.md +27 -0
- package/dist/docs/sdk/actions.md +0 -1
- package/dist/docs/sdk/content-security-policy.md +8 -7
- package/dist/docs/sdk/features.md +0 -1
- package/dist/docs/sdk/fields.md +2 -2
- package/dist/docs/sdk/index.md +0 -1
- package/dist/docs/sdk/plugins/create-experience.md +0 -1
- package/dist/docs/sdk/plugins/dynamic-experiences.md +2 -3
- package/dist/docs/sdk/plugins/window-object.md +0 -1
- package/dist/docs/sdk/predicates.md +0 -1
- package/dist/docs/workspaces.mdx +60 -1
- package/dist/dom-C3C6Fmwz.cjs +1 -0
- package/dist/dom-DGePXPpM.js +84 -0
- package/dist/esm/form/index.js +1 -1
- package/dist/esm/index.js +1020 -494
- package/dist/esm/plugins/create-experience/index.js +1 -1
- package/dist/esm/plugins/dom-observer/index.js +87 -0
- package/dist/esm/plugins/dynamic-experiences/index.js +133 -60
- package/dist/esm/plugins/experience-browser-storage/index.js +9 -6
- package/dist/esm/plugins/history-browser-storage/index.js +1 -1
- package/dist/esm/plugins/responses-browser-storage/index.js +1 -1
- package/dist/esm/plugins/single-page-app/index.js +1 -1
- package/dist/esm/plugins/web-components/index.js +1 -1
- package/dist/esm/server/index.js +23 -13
- package/dist/experience-instance-DImym3m_.js +419 -0
- package/dist/experience-instance-DygAdzbn.cjs +1 -0
- package/dist/rules-logic-D_CVBuv7.js +13 -0
- package/dist/rules-logic-H6P8xwHF.cjs +1 -0
- package/dist/types/client.d.ts +2 -2
- package/dist/types/entities/action/action.d.ts +6 -3
- package/dist/types/entities/action/action.d.ts.map +1 -1
- package/dist/types/entities/component/component.d.ts +39 -2
- package/dist/types/entities/component/component.d.ts.map +1 -1
- package/dist/types/entities/content/content.d.ts +37 -0
- package/dist/types/entities/content/content.d.ts.map +1 -0
- package/dist/types/entities/experience/experience.d.ts +69 -113
- package/dist/types/entities/experience/experience.d.ts.map +1 -1
- package/dist/types/entities/experience-instance/experience-instance.d.ts +17 -2
- package/dist/types/entities/experience-instance/experience-instance.d.ts.map +1 -1
- package/dist/types/entities/feature/feature.d.ts +26 -2
- package/dist/types/entities/feature/feature.d.ts.map +1 -1
- package/dist/types/entities/field/field.d.ts.map +1 -1
- package/dist/types/entities/framework/framework.d.ts +24 -3
- package/dist/types/entities/framework/framework.d.ts.map +1 -1
- package/dist/types/entities/plugin/plugin.d.ts +32 -1
- package/dist/types/entities/plugin/plugin.d.ts.map +1 -1
- package/dist/types/entities/predicate/predicate.d.ts +24 -3
- package/dist/types/entities/predicate/predicate.d.ts.map +1 -1
- package/dist/types/entities/session/session.d.ts +45 -0
- package/dist/types/entities/session/session.d.ts.map +1 -0
- package/dist/types/entities/step/step.d.ts +17 -2
- package/dist/types/entities/step/step.d.ts.map +1 -1
- package/dist/types/exports.d.ts +9 -3
- package/dist/types/exports.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lifecycle/lifecycle.d.ts +47 -0
- package/dist/types/lifecycle/lifecycle.d.ts.map +1 -0
- package/dist/types/lifecycle/rules-logic.d.ts +24 -0
- package/dist/types/lifecycle/rules-logic.d.ts.map +1 -0
- package/dist/types/lifecycle/state-snapshot.d.ts +37 -0
- package/dist/types/lifecycle/state-snapshot.d.ts.map +1 -0
- package/dist/types/logic-engine.d.ts +1 -1
- package/dist/types/logic-engine.d.ts.map +1 -1
- package/dist/types/picked.d.ts +141 -187
- package/dist/types/picked.d.ts.map +1 -1
- package/dist/types/plugins/dom-observer/index.d.ts +3 -0
- package/dist/types/plugins/dom-observer/index.d.ts.map +1 -0
- package/dist/types/plugins/dom-observer/plugin.d.ts +50 -0
- package/dist/types/plugins/dom-observer/plugin.d.ts.map +1 -0
- package/dist/types/plugins/dynamic-experiences/index.d.ts +2 -1
- package/dist/types/plugins/dynamic-experiences/index.d.ts.map +1 -1
- package/dist/types/plugins/dynamic-experiences/plugin.d.ts +18 -1
- package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
- package/dist/types/plugins/dynamic-experiences/types.d.ts +63 -1
- package/dist/types/plugins/dynamic-experiences/types.d.ts.map +1 -1
- package/dist/types/plugins/experience-browser-storage/plugin.d.ts.map +1 -1
- package/dist/types/plugins/web-components/plugin.d.ts.map +1 -1
- package/dist/types/server/index.d.ts +7 -0
- package/dist/types/server/index.d.ts.map +1 -1
- package/dist/types/utils/events.d.ts +1 -1
- package/dist/types/utils/events.d.ts.map +1 -1
- package/dist/types/utils/picked-config.d.ts +12 -4
- package/dist/types/utils/picked-config.d.ts.map +1 -1
- package/dist/types/utils/rules-logic.d.ts +4 -9
- package/dist/types/utils/rules-logic.d.ts.map +1 -1
- package/package.json +12 -2
- package/dist/api-NpGrXHyM.cjs +0 -1
- package/dist/docs/reference/index.md +0 -11
- package/dist/docs/tooling/index.md +0 -19
- package/dist/experience-instance-DC2gKwA6.cjs +0 -1
- package/dist/experience-instance-DJaNc5Ob.js +0 -486
- package/dist/rules-logic-BTIxfrJJ.js +0 -240
- package/dist/rules-logic-DNLOv90w.cjs +0 -1
- package/dist/types/utils/state-snaphot.d.ts +0 -26
- package/dist/types/utils/state-snaphot.d.ts.map +0 -1
- package/dist/types/utils/user-session.d.ts +0 -12
- package/dist/types/utils/user-session.d.ts.map +0 -1
- /package/dist/docs/{tooling/chrome-extension.md → chrome-extension.md} +0 -0
- /package/dist/docs/{tooling/gtm-template.md → gtm-template.md} +0 -0
- /package/dist/docs/{tooling/mcp.md → mcp.md} +0 -0
- /package/dist/docs/{tooling/vite-plugin.md → vite-plugin.md} +0 -0
- /package/dist/{id-Bv44ilEo.cjs → id-Cg7fVl1O.cjs} +0 -0
- /package/dist/{id-CRqy981z.js → id-n2yrHvNo.js} +0 -0
- /package/dist/{object-C82v79xc.js → object-DvUDOytC.js} +0 -0
- /package/dist/{object-CUJOjanb.cjs → object-ThO1DEyl.cjs} +0 -0
- /package/dist/{storage-B6lQtc4-.cjs → storage-BXSlLTRa.cjs} +0 -0
- /package/dist/{storage-6Xk0uAxB.js → storage-hOHCCXvP.js} +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: docs
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Authentication
|
|
6
|
+
|
|
7
|
+
The Picked API supports multiple authentication methods: email/password sessions, OAuth providers, magic links, and API keys. This guide covers each method and explains how to manage sessions and multi-factor authentication (MFA).
|
|
8
|
+
|
|
9
|
+
## Email and password login
|
|
10
|
+
|
|
11
|
+
The `sessionCreate` mutation authenticates a user with email and password and returns a session.
|
|
12
|
+
|
|
13
|
+
```graphql
|
|
14
|
+
mutation {
|
|
15
|
+
sessionCreate(input: {
|
|
16
|
+
email: "user@example.com"
|
|
17
|
+
password: "secure-password"
|
|
18
|
+
}) {
|
|
19
|
+
session {
|
|
20
|
+
accessToken
|
|
21
|
+
refreshToken
|
|
22
|
+
expiresAt
|
|
23
|
+
user {
|
|
24
|
+
id
|
|
25
|
+
email
|
|
26
|
+
}
|
|
27
|
+
aal
|
|
28
|
+
}
|
|
29
|
+
requiresMfa
|
|
30
|
+
mfaFactors {
|
|
31
|
+
id
|
|
32
|
+
factorType
|
|
33
|
+
status
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If the user has MFA enabled, the response sets `requiresMfa` to `true` and returns the enrolled `mfaFactors`. The caller must complete an MFA challenge before receiving a fully authenticated session. See the [MFA](#multi-factor-authentication-mfa) section below.
|
|
40
|
+
|
|
41
|
+
If MFA is not enabled, the response includes a `session` with an `accessToken`, a `refreshToken`, and an `expiresAt` timestamp.
|
|
42
|
+
|
|
43
|
+
## Account creation
|
|
44
|
+
|
|
45
|
+
New accounts are created with the `accountCreate` mutation:
|
|
46
|
+
|
|
47
|
+
```graphql
|
|
48
|
+
mutation {
|
|
49
|
+
accountCreate(input: {
|
|
50
|
+
email: "newuser@example.com"
|
|
51
|
+
password: "secure-password"
|
|
52
|
+
metadata: { firstName: "Jane", lastName: "Doe" }
|
|
53
|
+
}) {
|
|
54
|
+
user {
|
|
55
|
+
id
|
|
56
|
+
email
|
|
57
|
+
}
|
|
58
|
+
session {
|
|
59
|
+
accessToken
|
|
60
|
+
refreshToken
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The `metadata` field is optional and accepts arbitrary JSON for storing profile information. When email verification is required, the returned session is `null` until the email is verified.
|
|
67
|
+
|
|
68
|
+
## Email verification
|
|
69
|
+
|
|
70
|
+
After account creation, the user receives a verification email containing a token. The `accountVerifyEmail` mutation completes verification:
|
|
71
|
+
|
|
72
|
+
```graphql
|
|
73
|
+
mutation {
|
|
74
|
+
accountVerifyEmail(input: {
|
|
75
|
+
email: "newuser@example.com"
|
|
76
|
+
token: "verification-token-from-email"
|
|
77
|
+
}) {
|
|
78
|
+
session {
|
|
79
|
+
accessToken
|
|
80
|
+
refreshToken
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
If the verification email needs to be resent:
|
|
87
|
+
|
|
88
|
+
```graphql
|
|
89
|
+
mutation {
|
|
90
|
+
accountResendVerification(input: {
|
|
91
|
+
email: "newuser@example.com"
|
|
92
|
+
}) {
|
|
93
|
+
success
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## OAuth flow (Google)
|
|
99
|
+
|
|
100
|
+
The API supports Google as an OAuth provider. The flow has two steps.
|
|
101
|
+
|
|
102
|
+
### Step 1: Get the authorization URL
|
|
103
|
+
|
|
104
|
+
```graphql
|
|
105
|
+
mutation {
|
|
106
|
+
oauthAuthorize(input: {
|
|
107
|
+
provider: GOOGLE
|
|
108
|
+
redirectUrl: "https://app.example.com/auth/callback"
|
|
109
|
+
scopes: ["openid", "email", "profile"]
|
|
110
|
+
}) {
|
|
111
|
+
url
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The caller redirects the user to the returned `url`. After the user authenticates with Google, they are redirected back to a URL on the same dashboard deployment with a `code` parameter, where the caller exchanges it for a session.
|
|
117
|
+
|
|
118
|
+
### Step 2: Exchange the code for a session
|
|
119
|
+
|
|
120
|
+
```graphql
|
|
121
|
+
mutation {
|
|
122
|
+
oauthCallback(input: {
|
|
123
|
+
provider: GOOGLE
|
|
124
|
+
code: "auth-code-from-google"
|
|
125
|
+
}) {
|
|
126
|
+
session {
|
|
127
|
+
accessToken
|
|
128
|
+
refreshToken
|
|
129
|
+
expiresAt
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Magic links
|
|
136
|
+
|
|
137
|
+
Magic links allow passwordless authentication via email.
|
|
138
|
+
|
|
139
|
+
### Send a magic link
|
|
140
|
+
|
|
141
|
+
```graphql
|
|
142
|
+
mutation {
|
|
143
|
+
magicLinkSend(input: {
|
|
144
|
+
email: "user@example.com"
|
|
145
|
+
redirectUrl: "https://app.example.com/auth/verify"
|
|
146
|
+
}) {
|
|
147
|
+
success
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The user receives an email containing a link with a token.
|
|
153
|
+
|
|
154
|
+
### Verify the magic link
|
|
155
|
+
|
|
156
|
+
```graphql
|
|
157
|
+
mutation {
|
|
158
|
+
magicLinkVerify(input: {
|
|
159
|
+
email: "user@example.com"
|
|
160
|
+
token: "token-from-magic-link"
|
|
161
|
+
}) {
|
|
162
|
+
session {
|
|
163
|
+
accessToken
|
|
164
|
+
refreshToken
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Password management
|
|
171
|
+
|
|
172
|
+
### Reset a forgotten password
|
|
173
|
+
|
|
174
|
+
Send a password reset email:
|
|
175
|
+
|
|
176
|
+
```graphql
|
|
177
|
+
mutation {
|
|
178
|
+
passwordResetSend(input: {
|
|
179
|
+
email: "user@example.com"
|
|
180
|
+
redirectUrl: "https://app.example.com/reset-password"
|
|
181
|
+
}) {
|
|
182
|
+
success
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Complete the reset with the token from the email:
|
|
188
|
+
|
|
189
|
+
```graphql
|
|
190
|
+
mutation {
|
|
191
|
+
passwordReset(input: {
|
|
192
|
+
email: "user@example.com"
|
|
193
|
+
token: "reset-token-from-email"
|
|
194
|
+
newPassword: "new-secure-password"
|
|
195
|
+
}) {
|
|
196
|
+
session {
|
|
197
|
+
accessToken
|
|
198
|
+
refreshToken
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Update an existing password
|
|
205
|
+
|
|
206
|
+
This mutation requires authentication:
|
|
207
|
+
|
|
208
|
+
```graphql
|
|
209
|
+
mutation {
|
|
210
|
+
passwordUpdate(input: {
|
|
211
|
+
currentPassword: "old-password"
|
|
212
|
+
newPassword: "new-secure-password"
|
|
213
|
+
}) {
|
|
214
|
+
success
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Multi-factor authentication (MFA)
|
|
220
|
+
|
|
221
|
+
MFA adds a second layer of security using phone-based verification. The API supports enrolling, challenging, verifying, and unenrolling MFA factors. All MFA mutations require authentication.
|
|
222
|
+
|
|
223
|
+
### Enroll a factor
|
|
224
|
+
|
|
225
|
+
```graphql
|
|
226
|
+
mutation {
|
|
227
|
+
mfaFactorEnroll(input: {
|
|
228
|
+
factorType: PHONE
|
|
229
|
+
phone: "+15551234567"
|
|
230
|
+
friendlyName: "My Phone"
|
|
231
|
+
}) {
|
|
232
|
+
factor {
|
|
233
|
+
id
|
|
234
|
+
factorType
|
|
235
|
+
status
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
A newly enrolled factor has a status of `UNVERIFIED` until confirmed via a challenge.
|
|
242
|
+
|
|
243
|
+
### Send a challenge
|
|
244
|
+
|
|
245
|
+
```graphql
|
|
246
|
+
mutation {
|
|
247
|
+
mfaChallengeSend(input: {
|
|
248
|
+
factorId: "factor-id"
|
|
249
|
+
}) {
|
|
250
|
+
challengeId
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
This sends a verification code to the enrolled phone number.
|
|
256
|
+
|
|
257
|
+
### Verify the challenge
|
|
258
|
+
|
|
259
|
+
```graphql
|
|
260
|
+
mutation {
|
|
261
|
+
mfaChallengeVerify(input: {
|
|
262
|
+
factorId: "factor-id"
|
|
263
|
+
challengeId: "challenge-id"
|
|
264
|
+
code: "123456"
|
|
265
|
+
}) {
|
|
266
|
+
session {
|
|
267
|
+
accessToken
|
|
268
|
+
refreshToken
|
|
269
|
+
aal
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
On success, the session is upgraded to Authenticator Assurance Level `AAL2`. The factor status changes to `VERIFIED`.
|
|
276
|
+
|
|
277
|
+
### Unenroll a factor
|
|
278
|
+
|
|
279
|
+
```graphql
|
|
280
|
+
mutation {
|
|
281
|
+
mfaFactorUnenroll(input: {
|
|
282
|
+
factorId: "factor-id"
|
|
283
|
+
}) {
|
|
284
|
+
success
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Using a session
|
|
290
|
+
|
|
291
|
+
After obtaining a session, the caller includes the access token in the `Authorization` header of every authenticated request:
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
Authorization: Bearer <accessToken>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
The `expiresAt` field on the session indicates when the access token expires. The caller should refresh the session before the token expires.
|
|
298
|
+
|
|
299
|
+
### Authenticator assurance levels
|
|
300
|
+
|
|
301
|
+
The session includes an `aal` field indicating the authentication strength:
|
|
302
|
+
|
|
303
|
+
| Level | Meaning |
|
|
304
|
+
|-------|---------|
|
|
305
|
+
| `AAL1` | Single-factor authentication (email/password, OAuth, or magic link) |
|
|
306
|
+
| `AAL2` | Multi-factor authentication (MFA challenge completed) |
|
|
307
|
+
|
|
308
|
+
Some operations may require `AAL2`. If a caller with `AAL1` attempts such an operation, the API returns an authorization error.
|
|
309
|
+
|
|
310
|
+
## Session refresh
|
|
311
|
+
|
|
312
|
+
Access tokens are short-lived. The `sessionRefresh` mutation exchanges a refresh token for a new session:
|
|
313
|
+
|
|
314
|
+
```graphql
|
|
315
|
+
mutation {
|
|
316
|
+
sessionRefresh(input: {
|
|
317
|
+
refreshToken: "v1.refresh-token..."
|
|
318
|
+
}) {
|
|
319
|
+
session {
|
|
320
|
+
accessToken
|
|
321
|
+
refreshToken
|
|
322
|
+
expiresAt
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
The response includes a new `accessToken` and a new `refreshToken`. The previous refresh token is invalidated. This mutation does not require the `Authorization` header.
|
|
329
|
+
|
|
330
|
+
## API key authentication
|
|
331
|
+
|
|
332
|
+
API keys provide long-lived, scoped access to the API. They are typically used for server-to-server integrations and automated workflows.
|
|
333
|
+
|
|
334
|
+
To authenticate with an API key, include it in the `x-api-key` header:
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
x-api-key: pk_live_abc123...
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
API keys are scoped to an organization and optionally to a specific project. They carry a defined set of [authorization scopes](./authorization-and-scopes.md) that determine which operations the key can perform.
|
|
341
|
+
|
|
342
|
+
See [API Keys](/api-keys) for managing keys from the dashboard UI.
|
|
343
|
+
|
|
344
|
+
### Creating an API key
|
|
345
|
+
|
|
346
|
+
The `apiKeyCreate` mutation creates a new API key. This mutation is only available to session-authenticated users (not other API keys):
|
|
347
|
+
|
|
348
|
+
```graphql
|
|
349
|
+
mutation {
|
|
350
|
+
apiKeyCreate(input: {
|
|
351
|
+
organizationId: "org-id"
|
|
352
|
+
name: "CI/CD Pipeline Key"
|
|
353
|
+
projectId: "project-id"
|
|
354
|
+
scopes: [ORGANIZATION_READ, ORGANIZATION_PROJECTS_READ]
|
|
355
|
+
}) {
|
|
356
|
+
apiKey
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
The full API key string is returned only once in the response. Store it securely; it cannot be retrieved again.
|
|
362
|
+
|
|
363
|
+
The requested scopes must be a subset of the creating user's scopes. See [Authorization and Scopes](./authorization-and-scopes.md) for more details.
|
|
364
|
+
|
|
365
|
+
### Revoking an API key
|
|
366
|
+
|
|
367
|
+
```graphql
|
|
368
|
+
mutation {
|
|
369
|
+
apiKeyRevoke(input: {
|
|
370
|
+
keyId: "key-id"
|
|
371
|
+
organizationId: "org-id"
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Revoked keys are immediately invalidated and cannot be used for further requests.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: docs
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Authorization and scopes
|
|
6
|
+
|
|
7
|
+
The Picked API uses a scope-based authorization model. Every authenticated caller (session user or API key) carries a set of scopes that determine which operations they can perform. This guide explains the scope system, roles, and how permissions are resolved.
|
|
8
|
+
|
|
9
|
+
## Scope naming format
|
|
10
|
+
|
|
11
|
+
Scopes follow the `entity/action` convention:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
organization/read
|
|
15
|
+
organization_members/create
|
|
16
|
+
organization_projects/delete
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- **entity** — a lowercase name using underscores for multi-word entities (e.g., `organization_members`, `organization_api_keys`)
|
|
20
|
+
- **action** — one of `create`, `read`, `update`, or `delete`
|
|
21
|
+
|
|
22
|
+
In the GraphQL schema, scopes are represented as the `Scope` enum using uppercase with underscores (e.g., `ORGANIZATION_READ`, `ORGANIZATION_MEMBERS_CREATE`). Both formats refer to the same scope.
|
|
23
|
+
|
|
24
|
+
## Full scope list
|
|
25
|
+
|
|
26
|
+
The following scopes are available:
|
|
27
|
+
|
|
28
|
+
### Organization
|
|
29
|
+
|
|
30
|
+
| Scope | GraphQL Enum | Description |
|
|
31
|
+
|-------|-------------|-------------|
|
|
32
|
+
| `organization/create` | `ORGANIZATION_CREATE` | Create new organizations |
|
|
33
|
+
| `organization/read` | `ORGANIZATION_READ` | View organization details |
|
|
34
|
+
| `organization/update` | `ORGANIZATION_UPDATE` | Update organization name, slug, and settings |
|
|
35
|
+
| `organization/delete` | `ORGANIZATION_DELETE` | Delete an organization |
|
|
36
|
+
|
|
37
|
+
### Organization members
|
|
38
|
+
|
|
39
|
+
| Scope | GraphQL Enum | Description |
|
|
40
|
+
|-------|-------------|-------------|
|
|
41
|
+
| `organization_members/create` | `ORGANIZATION_MEMBERS_CREATE` | Add members to an organization |
|
|
42
|
+
| `organization_members/read` | `ORGANIZATION_MEMBERS_READ` | View organization member list |
|
|
43
|
+
| `organization_members/update` | `ORGANIZATION_MEMBERS_UPDATE` | Update member roles |
|
|
44
|
+
| `organization_members/delete` | `ORGANIZATION_MEMBERS_DELETE` | Remove members from an organization |
|
|
45
|
+
|
|
46
|
+
### Organization invitations
|
|
47
|
+
|
|
48
|
+
| Scope | GraphQL Enum | Description |
|
|
49
|
+
|-------|-------------|-------------|
|
|
50
|
+
| `organization_invitations/create` | `ORGANIZATION_INVITATIONS_CREATE` | Send invitations to join an organization |
|
|
51
|
+
| `organization_invitations/read` | `ORGANIZATION_INVITATIONS_READ` | View pending invitations |
|
|
52
|
+
| `organization_invitations/delete` | `ORGANIZATION_INVITATIONS_DELETE` | Revoke pending invitations |
|
|
53
|
+
|
|
54
|
+
### Organization projects
|
|
55
|
+
|
|
56
|
+
| Scope | GraphQL Enum | Description |
|
|
57
|
+
|-------|-------------|-------------|
|
|
58
|
+
| `organization_projects/create` | `ORGANIZATION_PROJECTS_CREATE` | Create new projects |
|
|
59
|
+
| `organization_projects/read` | `ORGANIZATION_PROJECTS_READ` | View project details |
|
|
60
|
+
| `organization_projects/update` | `ORGANIZATION_PROJECTS_UPDATE` | Update project settings |
|
|
61
|
+
| `organization_projects/delete` | `ORGANIZATION_PROJECTS_DELETE` | Delete projects |
|
|
62
|
+
|
|
63
|
+
### Organization API keys
|
|
64
|
+
|
|
65
|
+
| Scope | GraphQL Enum | Description |
|
|
66
|
+
|-------|-------------|-------------|
|
|
67
|
+
| `organization_api_keys/create` | `ORGANIZATION_API_KEYS_CREATE` | Create new API keys |
|
|
68
|
+
| `organization_api_keys/read` | `ORGANIZATION_API_KEYS_READ` | View API keys (prefix only, not the full key) |
|
|
69
|
+
| `organization_api_keys/delete` | `ORGANIZATION_API_KEYS_DELETE` | Revoke API keys |
|
|
70
|
+
|
|
71
|
+
### Organization integrations
|
|
72
|
+
|
|
73
|
+
| Scope | GraphQL Enum | Description |
|
|
74
|
+
|-------|-------------|-------------|
|
|
75
|
+
| `organization_integrations/create` | `ORGANIZATION_INTEGRATIONS_CREATE` | Set up new integrations |
|
|
76
|
+
| `organization_integrations/read` | `ORGANIZATION_INTEGRATIONS_READ` | View integration configurations |
|
|
77
|
+
| `organization_integrations/update` | `ORGANIZATION_INTEGRATIONS_UPDATE` | Modify integration settings |
|
|
78
|
+
| `organization_integrations/delete` | `ORGANIZATION_INTEGRATIONS_DELETE` | Remove integrations |
|
|
79
|
+
|
|
80
|
+
### Organization reports
|
|
81
|
+
|
|
82
|
+
| Scope | GraphQL Enum | Description |
|
|
83
|
+
|-------|-------------|-------------|
|
|
84
|
+
| `organization_reports/create` | `ORGANIZATION_REPORTS_CREATE` | Create new reports |
|
|
85
|
+
| `organization_reports/read` | `ORGANIZATION_REPORTS_READ` | View reports |
|
|
86
|
+
| `organization_reports/update` | `ORGANIZATION_REPORTS_UPDATE` | Update report settings |
|
|
87
|
+
| `organization_reports/delete` | `ORGANIZATION_REPORTS_DELETE` | Delete reports |
|
|
88
|
+
|
|
89
|
+
## Roles
|
|
90
|
+
|
|
91
|
+
Organization members are assigned one of two roles:
|
|
92
|
+
|
|
93
|
+
### Owner
|
|
94
|
+
|
|
95
|
+
Owners have full control within their organization. Their default scopes include:
|
|
96
|
+
|
|
97
|
+
- All `organization/*` scopes (`read`, `update`, `delete`)
|
|
98
|
+
- All `organization_members/*` scopes
|
|
99
|
+
- All `organization_invitations/*` scopes
|
|
100
|
+
- `organization_projects/read`, `organization_projects/update`, `organization_projects/delete`
|
|
101
|
+
- All `organization_api_keys/*` scopes
|
|
102
|
+
- All `organization_integrations/*` scopes
|
|
103
|
+
- `organization_reports/read`
|
|
104
|
+
|
|
105
|
+
### Member
|
|
106
|
+
|
|
107
|
+
Members have broad access but cannot delete the organization or manage other members. Their default scopes include:
|
|
108
|
+
|
|
109
|
+
- `organization/read`, `organization/update`
|
|
110
|
+
- `organization_members/read`
|
|
111
|
+
- `organization_invitations/read`
|
|
112
|
+
- `organization_projects/read`, `organization_projects/update`, `organization_projects/delete`
|
|
113
|
+
- All `organization_api_keys/*` scopes
|
|
114
|
+
- All `organization_integrations/*` scopes
|
|
115
|
+
- `organization_reports/read`
|
|
116
|
+
|
|
117
|
+
The key differences between owner and member:
|
|
118
|
+
|
|
119
|
+
| Capability | Owner | Member |
|
|
120
|
+
|-----------|-------|--------|
|
|
121
|
+
| Delete the organization | Yes | No |
|
|
122
|
+
| Invite new members | Yes | No |
|
|
123
|
+
| Remove members | Yes | No |
|
|
124
|
+
| Update member roles | Yes | No |
|
|
125
|
+
| Revoke invitations | Yes | No |
|
|
126
|
+
| Manage projects and integrations | Yes | Yes |
|
|
127
|
+
| Manage API keys | Yes | Yes |
|
|
128
|
+
| View reports | Yes | Yes |
|
|
129
|
+
|
|
130
|
+
## How scopes are resolved
|
|
131
|
+
|
|
132
|
+
When an authenticated request reaches the API, the authorization system resolves scopes as follows:
|
|
133
|
+
|
|
134
|
+
1. **Session-authenticated users** — the API looks up the caller's role within the target organization and assigns the default scopes for that role.
|
|
135
|
+
2. **API key requests** — the API uses the scopes that were assigned to the key at creation time. These scopes are always a subset of the creating user's scopes.
|
|
136
|
+
|
|
137
|
+
For operations that target a specific organization (most queries and mutations), the caller must have the required scope within that organization. For root-level operations like `organizationCreate`, the scope is resolved at the account level.
|
|
138
|
+
|
|
139
|
+
If the caller lacks the required scope, the API returns an authorization error with a `FORBIDDEN` code. See [Error Handling](./error-handling.md) for details.
|
|
140
|
+
|
|
141
|
+
## Operations and required scopes
|
|
142
|
+
|
|
143
|
+
The following table maps common operations to the scopes they require:
|
|
144
|
+
|
|
145
|
+
### Queries
|
|
146
|
+
|
|
147
|
+
| Query | Required Scope |
|
|
148
|
+
|-------|---------------|
|
|
149
|
+
| `organization` | `organization/read` |
|
|
150
|
+
| `organizations` | `organization/read` |
|
|
151
|
+
| `project` | `organization_projects/read` |
|
|
152
|
+
| `apiKeys` | `organization_api_keys/read` |
|
|
153
|
+
| `shopifyWebhooks` | `organization_integrations/read` |
|
|
154
|
+
| `shopifyMetafieldConfigs` | `organization_integrations/read` |
|
|
155
|
+
| `shopifyProduct` / `shopifyProducts` | `organization_integrations/read` |
|
|
156
|
+
| `report` | `organization_reports/read` |
|
|
157
|
+
|
|
158
|
+
### Mutations
|
|
159
|
+
|
|
160
|
+
| Mutation | Required Scope |
|
|
161
|
+
|----------|---------------|
|
|
162
|
+
| `organizationCreate` | `organization/create` |
|
|
163
|
+
| `organizationUpdate` | `organization/update` |
|
|
164
|
+
| `organizationDelete` | `organization/delete` |
|
|
165
|
+
| `organizationMemberInvite` | `organization_invitations/create` |
|
|
166
|
+
| `organizationInvitationRevoke` | `organization_invitations/delete` |
|
|
167
|
+
| `organizationMemberRemove` | `organization_members/delete` |
|
|
168
|
+
| `organizationMemberRoleUpdate` | `organization_members/update` |
|
|
169
|
+
| `projectCreate` | `organization_projects/create` |
|
|
170
|
+
| `apiKeyCreate` | `organization_api_keys/create` |
|
|
171
|
+
| `apiKeyRevoke` | `organization_api_keys/delete` |
|
|
172
|
+
| `shopifyWebhookCreate` | `organization_integrations/create` |
|
|
173
|
+
| `shopifyWebhookUpdate` | `organization_integrations/update` |
|
|
174
|
+
| `shopifyWebhookDelete` | `organization_integrations/delete` |
|
|
175
|
+
| `shopifyMetafieldConfigCreate` | `organization_integrations/create` |
|
|
176
|
+
| `shopifyMetafieldConfigUpdate` | `organization_integrations/update` |
|
|
177
|
+
| `shopifyMetafieldConfigDelete` | `organization_integrations/delete` |
|
|
178
|
+
| `reportCreate` | `organization_reports/create` |
|
|
179
|
+
| `reportUpdate` | `organization_reports/update` |
|
|
180
|
+
| `reportDelete` | `organization_reports/delete` |
|
|
181
|
+
|
|
182
|
+
Some mutations do not require organization-level scopes because they operate on the caller's own data:
|
|
183
|
+
|
|
184
|
+
| Mutation | Requirement |
|
|
185
|
+
|----------|------------|
|
|
186
|
+
| `sessionCreate` / `sessionRefresh` | No authentication required |
|
|
187
|
+
| `accountCreate` / `accountVerifyEmail` | No authentication required |
|
|
188
|
+
| `oauthAuthorize` / `oauthCallback` | No authentication required |
|
|
189
|
+
| `magicLinkSend` / `magicLinkVerify` | No authentication required |
|
|
190
|
+
| `passwordResetSend` / `passwordReset` | No authentication required |
|
|
191
|
+
| `passwordUpdate` | Authenticated session (no specific scope) |
|
|
192
|
+
| `organizationInvitationAccept` / `organizationInvitationDecline` | Authenticated session (no specific scope) |
|
|
193
|
+
| `mfaFactorEnroll` / `mfaChallengeSend` / `mfaChallengeVerify` / `mfaFactorUnenroll` | Authenticated session (no specific scope) |
|
|
194
|
+
|
|
195
|
+
## Querying scopes
|
|
196
|
+
|
|
197
|
+
### Root-level scopes
|
|
198
|
+
|
|
199
|
+
The `viewerScopes` query returns the root-level scopes for the authenticated caller:
|
|
200
|
+
|
|
201
|
+
```graphql
|
|
202
|
+
query {
|
|
203
|
+
viewerScopes
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Response:
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"data": {
|
|
212
|
+
"viewerScopes": [
|
|
213
|
+
"ORGANIZATION_CREATE"
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Organization-level scopes
|
|
220
|
+
|
|
221
|
+
Each `Organization` object exposes a `viewerScopes` field that returns the scopes the caller has within that specific organization:
|
|
222
|
+
|
|
223
|
+
```graphql
|
|
224
|
+
query {
|
|
225
|
+
organization(input: { slug: "my-company" }) {
|
|
226
|
+
id
|
|
227
|
+
name
|
|
228
|
+
viewerScopes
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Response:
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"data": {
|
|
238
|
+
"organization": {
|
|
239
|
+
"id": "org_abc123",
|
|
240
|
+
"name": "My Company",
|
|
241
|
+
"viewerScopes": [
|
|
242
|
+
"ORGANIZATION_READ",
|
|
243
|
+
"ORGANIZATION_UPDATE",
|
|
244
|
+
"ORGANIZATION_MEMBERS_READ",
|
|
245
|
+
"ORGANIZATION_PROJECTS_READ",
|
|
246
|
+
"ORGANIZATION_PROJECTS_UPDATE",
|
|
247
|
+
"ORGANIZATION_PROJECTS_DELETE",
|
|
248
|
+
"ORGANIZATION_API_KEYS_CREATE",
|
|
249
|
+
"ORGANIZATION_API_KEYS_READ",
|
|
250
|
+
"ORGANIZATION_API_KEYS_DELETE",
|
|
251
|
+
"ORGANIZATION_INTEGRATIONS_CREATE",
|
|
252
|
+
"ORGANIZATION_INTEGRATIONS_READ",
|
|
253
|
+
"ORGANIZATION_INTEGRATIONS_UPDATE",
|
|
254
|
+
"ORGANIZATION_INTEGRATIONS_DELETE",
|
|
255
|
+
"ORGANIZATION_REPORTS_READ"
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
This is useful for building UIs that adapt based on the caller's permissions — for example, hiding a "Delete Organization" button when the caller lacks `ORGANIZATION_DELETE`.
|