@picked/client 0.0.84 → 0.0.85-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/{api-Dauhcq05.cjs → api-C0GTH5AE.cjs} +1 -1
  3. package/dist/{api-Dpa-71Qe.js → api-CnZly-e2.js} +1 -1
  4. package/dist/cjs/index.js +2 -2
  5. package/dist/cjs/logic-engine.js +1 -0
  6. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  7. package/dist/cjs/server/index.js +1 -1
  8. package/dist/docs/how-to/authenticate-the-cli.md +4 -0
  9. package/dist/docs/how-to/authenticate-with-a-password.md +11 -3
  10. package/dist/docs/how-to/call-an-external-api-from-a-workflow.md +68 -0
  11. package/dist/docs/how-to/connect-the-mcp-server.md +8 -0
  12. package/dist/docs/how-to/set-up-mfa.md +58 -19
  13. package/dist/docs/reference/dashboard/external-apis.md +65 -0
  14. package/dist/docs/reference/dashboard/lead-search.md +149 -0
  15. package/dist/docs/reference/dashboard/workflow-runs.md +79 -0
  16. package/dist/docs/reference/glossary.md +10 -0
  17. package/dist/docs/reference/management-api/authentication.md +15 -8
  18. package/dist/docs/reference/management-api/authorization-and-scopes.md +5 -1
  19. package/dist/docs/reference/management-api/error-handling.md +2 -3
  20. package/dist/docs/reference/sdk/analytics-events.md +2 -3
  21. package/dist/docs/tutorials/first-api-call.md +13 -6
  22. package/dist/esm/index.js +283 -476
  23. package/dist/esm/logic-engine.js +208 -0
  24. package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
  25. package/dist/esm/server/index.js +2 -2
  26. package/dist/plugin-BjDkXYhs.cjs +1 -0
  27. package/dist/{plugin-B8tQck_k.js → plugin-GO5HgKAn.js} +80 -45
  28. package/dist/{preview-unavailable-CBjPwMqm.cjs → preview-unavailable-Bhv0k6OM.cjs} +1 -1
  29. package/dist/{preview-unavailable-DrO9PFzS.js → preview-unavailable-R4IEQXV_.js} +1 -1
  30. package/dist/{provider-DZBClIc7.js → provider-BsUkKYzJ.js} +1 -1
  31. package/dist/{provider-DFJQEjV6.cjs → provider-UnhXGWr5.cjs} +1 -1
  32. package/dist/types/logic-engine.d.ts +9 -0
  33. package/dist/types/logic-engine.d.ts.map +1 -1
  34. package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
  35. package/package.json +18 -7
  36. package/dist/plugin-BmjrsiJ2.cjs +0 -1
@@ -5,38 +5,60 @@ layout: docs
5
5
 
6
6
  # How to set up MFA
7
7
 
8
- This guide shows you how to add a second factor to an account. Every MFA mutation needs
9
- authentication.
8
+ This guide shows you how to add an authenticator app to an account, and how to sign in
9
+ with it. Picked supports TOTP authenticator apps only, such as Google Authenticator.
10
+ Every MFA mutation needs authentication.
10
11
 
11
- ## 1. Add a factor
12
+ ## Who must use MFA
13
+
14
+ Picked staff, and every member of an organization that requires MFA, must sign in with an
15
+ authenticator app. For these users, a session at `AAL1` can only read `user` and call the
16
+ MFA mutations. Every other operation returns the error code `MFA_REQUIRED` until the
17
+ session reaches `AAL2`. The `user.mfa` field tells you where a user stands:
18
+
19
+ ```graphql
20
+ query {
21
+ user {
22
+ mfa { required reason enrolled satisfied }
23
+ }
24
+ }
25
+ ```
26
+
27
+ | Field | Meaning |
28
+ | --- | --- |
29
+ | `required` | The user must hold an `AAL2` session to use the fields that need it. |
30
+ | `reason` | `STAFF` or `ORGANIZATION`. Empty when `required` is false. |
31
+ | `enrolled` | The user holds a verified authenticator. |
32
+ | `satisfied` | The session that made the request meets the requirement. |
33
+
34
+ ## 1. Add an authenticator
12
35
 
13
36
  ```graphql
14
37
  mutation {
15
- mfaFactorEnroll(input: {
16
- factorType: PHONE
17
- phone: "+15551234567"
18
- friendlyName: "My Phone"
19
- }) {
38
+ mfaFactorEnroll(input: { friendlyName: "Work phone" }) {
20
39
  factor { id factorType status }
40
+ totp { qrCode secret uri }
21
41
  }
22
42
  }
23
43
  ```
24
44
 
25
- A new factor has the status `UNVERIFIED`.
45
+ A new factor has the status `UNVERIFIED`. Show `qrCode` (SVG markup) for the user to scan,
46
+ or `secret` for the user to enter into the app. The API returns it one time only.
47
+ The mutation first deletes the caller's factors that are not verified, so a setup that did
48
+ not finish does not block a new one.
26
49
 
27
- ## 2. Send a challenge
50
+ ## 2. Open a challenge
28
51
 
29
52
  ```graphql
30
53
  mutation {
31
- mfaChallengeSend(input: { factorId: "factor-id" }) {
54
+ mfaChallengeCreate(input: { factorId: "factor-id" }) {
32
55
  challengeId
56
+ expiresAt
33
57
  }
34
58
  }
35
59
  ```
36
60
 
37
- This sends a code to the telephone number of the factor.
38
-
39
- ## 3. Verify the challenge
61
+ ## 3. Verify the code
40
62
 
41
63
  ```graphql
42
64
  mutation {
@@ -50,9 +72,17 @@ mutation {
50
72
  }
51
73
  ```
52
74
 
53
- The session moves to `AAL2`, and the factor status changes to `VERIFIED`.
75
+ The session moves to `AAL2`, and the factor status changes to `VERIFIED`. Use the returned
76
+ tokens from this point. A wrong code returns `MFA_CHALLENGE_FAILED`.
77
+
78
+ ## Sign in with an authenticator
54
79
 
55
- ## Remove a factor
80
+ After `sessionCreate`, the session is at `AAL1`. When `session.user.mfa.enrolled` is
81
+ `true`, do steps 2 and 3 against one of the user's verified factors. The query
82
+ `user { mfaFactors { id status } }` lists them. When `required` is `true` and `enrolled`
83
+ is `false`, do steps 1 to 3.
84
+
85
+ ## Remove an authenticator
56
86
 
57
87
  ```graphql
58
88
  mutation {
@@ -62,8 +92,17 @@ mutation {
62
92
  }
63
93
  ```
64
94
 
95
+ To remove a verified factor, the session must be at `AAL2`. An `AAL1` session receives
96
+ `MFA_REQUIRED`.
97
+
98
+ A user who must use MFA always keeps at least one authenticator. A request to remove
99
+ their last one fails with `VALIDATION_ERROR`. To replace an authenticator, add the new
100
+ one first, then remove the old one. A user with the `users/reset_mfa` scope clears the
101
+ last one with `userMfaReset`.
102
+
65
103
  ## Related
66
104
 
67
- - [Authorization and scopes](/reference/management-api/authorization-and-scopes) — which
68
- operations need `AAL2`.
69
- - [Authentication](/reference/management-api/authentication) — every mutation.
105
+ - [Authentication](/reference/management-api/authentication) — every mutation and the
106
+ assurance levels.
107
+ - [Authorization and scopes](/reference/management-api/authorization-and-scopes) — who
108
+ can reset the MFA of another user.
@@ -0,0 +1,65 @@
1
+ ---
2
+ type: reference
3
+ layout: docs
4
+ ---
5
+
6
+ # External APIs
7
+
8
+ An external API is an origin that this project's workflows are allowed to call with the
9
+ **External API** node.
10
+
11
+ ## Registration
12
+
13
+ | Field | Rule |
14
+ | --- | --- |
15
+ | Display name | Required. 1 to 120 characters. For you only; it does not go out with the request. |
16
+ | Origin | Required. The scheme, the host and the optional port, with no path, query, fragment or sign-in details. `http` or `https` only, on port 80 or 443. Must not point at a private, loopback or link-local address. One live registration per origin per project. |
17
+ | Authorization header | Optional. A header name and a value. The value is encrypted and is never returned. The dashboard shows the header name and a mask. |
18
+
19
+ To change an origin, delete the registration and register the new origin. The origin is
20
+ the registration's identity.
21
+
22
+ ## Node arguments
23
+
24
+ | Argument | Default | Rule |
25
+ | --- | --- | --- |
26
+ | `method` | — | Required. `GET`, `HEAD`, `POST`, `PUT`, `PATCH` or `DELETE`. |
27
+ | `url` | — | Required. The full address. Its origin must be registered to this project. |
28
+ | `headers` | none | A JSON object of headers to send. Picked refuses a credential here when you save. |
29
+ | `body` | none | The JSON body. Used by `POST`, `PUT` and `PATCH`. |
30
+ | `timeoutMs` | 10000 | 1000 to 30000. The time that the whole call has, with every retry. |
31
+ | `retry` | on | Whether to send the request again after a timeout, a transport error, a 408 or a 429. |
32
+ | `failOnErrorStatus` | on | Whether a status of 400 or more fails the node. |
33
+ | `recordBody` | on | Whether to record the answer's body. |
34
+
35
+ ## Output
36
+
37
+ | Name | Value |
38
+ | --- | --- |
39
+ | `status` | The HTTP status, or null when nothing answered. |
40
+ | `ok` | True for a status below 400. |
41
+ | `body` | The parsed JSON answer, or null when it is not JSON or was not recorded. |
42
+ | `bodyTruncated` | True when the answer was too large to record in full. |
43
+ | `durationMs` | The time the call took. |
44
+
45
+ ## Limits
46
+
47
+ - The request body is limited to 1 MiB.
48
+ - The recorded answer is limited to 256 KiB. A larger answer sets `bodyTruncated`.
49
+ - Picked records the status that asks the caller to redirect. Picked does not go to the
50
+ new address.
51
+ - Picked sends a request three times at most. Picked waits longer before each retry, and
52
+ waits for the time that a `Retry-After` header gives.
53
+
54
+ ## Errors
55
+
56
+ | Message | Cause |
57
+ | --- | --- |
58
+ | `no external API is registered for <origin>` | The URL's origin has no live registration in this project. |
59
+ | `the API answered <status>` | The status was 400 or more and `failOnErrorStatus` is on. |
60
+ | `the API did not answer in time` | Nothing answered within `timeoutMs`. |
61
+ | `the API could not be reached` | The connection failed. |
62
+ | `the URL is not allowed` | The URL points at a private, loopback or link-local address. |
63
+
64
+ Picked also refuses a workflow when you save it if a node holds a credential in
65
+ `headers`. Register the external API, and put the credential on the registration.
@@ -0,0 +1,149 @@
1
+ ---
2
+ type: reference
3
+ layout: docs
4
+ ---
5
+
6
+ # Lead search
7
+
8
+ A lead is one set of answers that a visitor gave to your flow. The **Leads** page lists
9
+ them, and it has one search box. The box accepts a plain word, a filter, or both. The
10
+ dashboard holds the query in the address, so you can share the link.
11
+
12
+ ## The three parts of a lead that a query reads
13
+
14
+ | Part | How to write it | Example |
15
+ | --- | --- | --- |
16
+ | Every captured answer | a plain word | `melbourne` |
17
+ | One label | `namespace.key` | `acme.status:sent` |
18
+ | One answer | the key of the field | `suburb:Sydney` |
19
+ | The capture date | `createdAt` | `createdAt>2026-08-01` |
20
+ | The visitor | `userId` | `userId:sess_7x2k9q3m1n4a` |
21
+
22
+ A path of two parts is a label, because a label namespace and a label key cannot contain a
23
+ full stop. A path of one part is the key of a field, unless the name is `createdAt` or
24
+ `userId`.
25
+
26
+ ## Operators
27
+
28
+ | Operator | Meaning | Example |
29
+ | --- | --- | --- |
30
+ | `:` | equal to | `details.phone:"0400000000"` |
31
+ | `~` | contains | `details.email~example.com` |
32
+ | `:*` | the lead has a value | `acme.status:*` |
33
+ | `>` `>=` `<` `<=` | compare a number or a date | `scoring.value>=50` |
34
+ | `-` | makes the clause negative | `-acme.status:*` |
35
+
36
+ Each part accepts a different set:
37
+
38
+ | Part | `:` | `~` | `:*` | `>` `>=` `<` `<=` |
39
+ | --- | --- | --- | --- | --- |
40
+ | Label | yes | yes | yes | yes |
41
+ | Answer | yes | yes | yes | no |
42
+ | `createdAt` | yes | no | no | yes |
43
+ | `userId` | yes | no | no | no |
44
+
45
+ ## How clauses combine
46
+
47
+ A space between two clauses means AND. The word `OR` between them means OR.
48
+
49
+ ```
50
+ acme.status:sent scoring.contacted:true
51
+ acme.status:sent OR acme.status:queued
52
+ ```
53
+
54
+ One query holds a maximum of 10 clauses. One query uses AND or OR, and not both: there is
55
+ no way to show which one comes first. A query that mixes them gives an error.
56
+
57
+ ## Values with a space
58
+
59
+ Put `"` at each end of a value that has a space. To search for `"` inside a value, write
60
+ `\"`.
61
+
62
+ ```
63
+ details.company:"Acme Corporation"
64
+ ```
65
+
66
+ ## Plain words
67
+
68
+ A plain word searches every answer that the visitor gave, and it includes the text inside
69
+ a JSON answer.
70
+
71
+ A plain word matches a whole word. `amy@example.com` finds the lead with that address, and
72
+ `example.com` finds nothing. To match a part of a value, name the field and use `~`:
73
+
74
+ ```
75
+ email-address~example.com
76
+ ```
77
+
78
+ Two plain words both must match.
79
+
80
+ ## Examples
81
+
82
+ **Plain words**
83
+
84
+ | Query | What it finds |
85
+ | --- | --- |
86
+ | `melbourne` | Leads with `melbourne` in an answer |
87
+ | `amy@example.com` | The lead with that address |
88
+ | `Amy Rockwell` | Leads with both words in their answers |
89
+ | `dental` | Leads with `dental` inside a JSON answer |
90
+
91
+ **Labels**
92
+
93
+ | Query | What it finds |
94
+ | --- | --- |
95
+ | `details.phone:"0400000000"` | Leads with that exact telephone number |
96
+ | `details.email~example.com` | Leads with an email label that holds `example.com` |
97
+ | `acme.status:sent` | Leads that the `acme` workflow sent |
98
+ | `acme.status:*` | Leads with an `acme` status |
99
+ | `-acme.status:*` | Leads with no `acme` status, which no workflow took |
100
+ | `scoring.value>=50` | Leads with a score of 50 or more |
101
+ | `scoring.contacted:true` | Leads with that boolean label set to true |
102
+ | `crm.payload:*` | Leads with a JSON label under `crm` |
103
+
104
+ **Answers and the capture date**
105
+
106
+ | Query | What it finds |
107
+ | --- | --- |
108
+ | `suburb:Sydney` | Leads whose `suburb` answer is `Sydney` |
109
+ | `email-address~rocketrides` | Leads whose `email-address` answer holds `rocketrides` |
110
+ | `quote-preferences:*` | Leads that answered `quote-preferences` |
111
+ | `createdAt>2026-08-01` | Leads captured after that date |
112
+ | `userId:sess_7x2k9q3m1n4a` | Leads captured for that visitor |
113
+
114
+ **More than one clause**
115
+
116
+ | Query | What it finds |
117
+ | --- | --- |
118
+ | `details.first_name:Amy scoring.contacted:true` | Both clauses are true |
119
+ | `acme.status:sent OR acme.status:queued` | Either clause is true |
120
+ | `Sydney details.phone:"0400000000"` | A plain word and a label clause together |
121
+
122
+ ## The `labels.` and `responses.` prefixes
123
+
124
+ A path takes an optional `labels.` or `responses.` prefix. Two cases need it:
125
+
126
+ - A label namespace with the name `labels` or `responses`: write
127
+ `labels.responses.status:sent`.
128
+ - The key of a field that holds a full stop: write `responses.quote.preferences:*`. Without
129
+ the prefix the query reads that path as a label.
130
+
131
+ A field with the name `createdAt` or `userId` also needs the prefix. A path of one part with
132
+ that name reads as the capture date or the visitor.
133
+
134
+ ## Errors
135
+
136
+ The search box shows an error, and the list does not change, for each of these:
137
+
138
+ | Cause | Example |
139
+ | --- | --- |
140
+ | A `"` with no second `"` | `details.company:"Acme` |
141
+ | A namespace or a key outside `a-z`, `0-9`, `_` and `-` | `Details.Phone:1` |
142
+ | An operator that the part does not accept | `createdAt~2026` |
143
+ | AND together with OR | `a.b:1 c.d:2 OR e.f:3` |
144
+ | More than 10 clauses | 11 clauses in one query |
145
+
146
+ ## Related
147
+
148
+ - [Fields](/reference/dashboard/fields) — the keys that name each answer
149
+ - [Glossary](/reference/glossary) — lead, label, and the other terms
@@ -0,0 +1,79 @@
1
+ ---
2
+ type: reference
3
+ layout: docs
4
+ ---
5
+
6
+ # Workflow runs
7
+
8
+ A run is one execution of a published workflow. An event or an action starts it. Picked
9
+ records each run, each node that the run reached, and each lead that the run created or
10
+ changed.
11
+
12
+ ## Where runs show
13
+
14
+ | Page | Address | Content |
15
+ | --- | --- | --- |
16
+ | Runs | `…/workflows/<workflow>/runs` | The runs of one workflow, the last run first. Open it with **Runs** in the workflow editor. |
17
+ | Run | `…/workflows/<workflow>/runs/<run>` | One run: its records, its tasks and its details. |
18
+ | Lead | `…/leads/<lead>` | The **Workflow runs** card. It lists the runs that created or changed the lead. |
19
+
20
+ A run starts only from a published version of the workspace. A change to a workflow that
21
+ you did not publish does not start runs.
22
+
23
+ ## Run status
24
+
25
+ | Status | Meaning |
26
+ | --- | --- |
27
+ | `Running` | The run started and did not stop yet. A **Wait** node can keep a run in this status for days. |
28
+ | `Completed` | Each node that the run reached did its work. |
29
+ | `Failed` | A node failed. The run stopped at that node. The run shows the reason. |
30
+
31
+ ## Records
32
+
33
+ The **Records** card lists each lead that the run created or changed. It shows each lead
34
+ one time.
35
+
36
+ | Label | Meaning |
37
+ | --- | --- |
38
+ | `Created` | A node of the run created the lead. This label replaces `Updated` for the same lead. |
39
+ | `Updated` | A node of the run changed the lead or its labels. |
40
+ | `Deleted` | A node of the run deleted the lead. This label replaces `Created` and `Updated` for the same lead. |
41
+
42
+ When a lead does not exist now, the card shows `Deleted lead` and the ID of the lead,
43
+ with no link.
44
+
45
+ ## Tasks
46
+
47
+ A task is one node that the run reached. The **Tasks** card lists the tasks in the order
48
+ that the run reached them.
49
+
50
+ | Column | Value |
51
+ | --- | --- |
52
+ | Name | The tool of a tool node, or the kind of node: **Start**, **Condition** or **Wait**. |
53
+ | Node | The ID of the node. It links to the node in the editor while the workflow has the node. |
54
+ | Status | `Running`, `Succeeded` or `Failed`. |
55
+ | Time | The time from the start of the task to its end. |
56
+ | Operations | The leads and the labels that the task created, changed or removed, the first operation first. A label shows its namespace and its key. It never shows its value. |
57
+
58
+ A condition task does not show which branch it chose.
59
+
60
+ ## Leads and runs
61
+
62
+ A lead lists a run when a node of that run created the lead, set one of its labels, or
63
+ removed one of its labels. A change to the labels of a lead is a change to the lead.
64
+
65
+ The **Workflow runs** card shows 20 runs. **Show older runs** adds the next 20.
66
+
67
+ ## Access
68
+
69
+ | Surface | Scopes |
70
+ | --- | --- |
71
+ | Runs, Run | `ORGANIZATION_WORKSPACE_READ` and `ORGANIZATION_PROJECTS_READ` |
72
+ | **Runs** in the workflow editor | `ORGANIZATION_WORKSPACE_READ` and `ORGANIZATION_PROJECTS_READ`. Without these scopes, the editor does not show **Runs**. |
73
+ | Workflow runs card | `ORGANIZATION_LEADS_READ` for the lead, and `ORGANIZATION_WORKSPACE_READ` and `ORGANIZATION_PROJECTS_READ` for its runs. Without the last two scopes, the card does not show. |
74
+
75
+ ## Deleted workflows
76
+
77
+ A run stays after you delete its workflow. The run page and the lead card show it as
78
+ **Deleted workflow**. The **Runs** page of a deleted workflow does not exist, and the run
79
+ page has no link to the editor.
@@ -92,6 +92,16 @@ documentation uses, and it names the others.
92
92
  | **Asset version** | One file that somebody uploaded for an asset. The library keeps the earlier versions. |
93
93
  | **Validation error** | A report that one field in a content entry does not match its schema. It carries the path to the exact field. |
94
94
 
95
+ ## Leads
96
+
97
+ | Term | What it is |
98
+ | --- | --- |
99
+ | **Lead** | One set of answers that a visitor gave to a flow. It belongs to the project, and it stays after you publish a new version. |
100
+ | **Response** | One answer in a lead, held under the key of the field that asked for it. |
101
+ | **Label** | A name and a value that you or a workflow puts on a lead. It sits outside the answers, and it stays when you change your fields. |
102
+ | **Namespace** | The first part of the name of a label, which groups related keys, such as `details` in `details.phone`. |
103
+ | **Label type** | How to read the value of a label: text, number, boolean, or JSON. |
104
+
95
105
  ## The SDK
96
106
 
97
107
  | Term | What it is |
@@ -29,16 +29,23 @@ The session holds an `aal` field, and it gives the strength of the authenticatio
29
29
  | Level | Meaning |
30
30
  |-------|---------|
31
31
  | `AAL1` | One factor: an email address and a password, OAuth, or a magic link |
32
- | `AAL2` | More than one factor: the user completed an MFA challenge |
32
+ | `AAL2` | Two factors: the user verified a code from an authenticator app |
33
33
 
34
- Some operations need `AAL2`. When a caller at `AAL1` tries one of them, the API
35
- returns an authorization error.
34
+ Picked staff, and every member of an organization that requires MFA, must use `AAL2`.
35
+ For these users, an `AAL1` session can only read `user` and call the MFA mutations. Every
36
+ other operation returns the error code `MFA_REQUIRED`. Read `session.user.mfa` after
37
+ `sessionCreate` to find out whether the user must verify, and see
38
+ [How to set up MFA](/how-to/set-up-mfa) for the steps.
39
+
40
+ The API checks the level when it creates a token. An OAuth token for an MCP client or the
41
+ CLI is created at consent, which needs `AAL2` for these users. The token stays valid
42
+ until it is revoked.
36
43
 
37
44
  ## The mutations
38
45
 
39
46
  | Mutation | What it does |
40
47
  | --- | --- |
41
- | `sessionCreate` | Authenticates an email address and a password. Returns a session, or `requiresMfa`. |
48
+ | `sessionCreate` | Authenticates an email address and a password. Returns a session at `AAL1`. `session.user.mfa` says whether the user must verify. |
42
49
  | `sessionRefresh` | Exchanges a refresh token for a new session. Needs no `Authorization` header. |
43
50
  | `accountCreate` | Creates an account. `metadata` takes any JSON. |
44
51
  | `accountVerifyEmail` | Completes email verification with the token from the email. |
@@ -50,10 +57,10 @@ returns an authorization error.
50
57
  | `passwordResetSend` | Sends a password reset email. |
51
58
  | `passwordReset` | Completes a reset with the token from the email. |
52
59
  | `passwordUpdate` | Changes a password. Needs authentication. |
53
- | `mfaFactorEnroll` | Adds an MFA factor. A new factor is `UNVERIFIED`. |
54
- | `mfaChallengeSend` | Sends a code to the telephone number of the factor. |
55
- | `mfaChallengeVerify` | Verifies a challenge. Moves the session to `AAL2`. |
56
- | `mfaFactorUnenroll` | Removes an MFA factor. |
60
+ | `mfaFactorEnroll` | Adds an authenticator app. A new factor is `UNVERIFIED`. Returns the QR code and the key one time. |
61
+ | `mfaChallengeCreate` | Opens a challenge against a factor. |
62
+ | `mfaChallengeVerify` | Verifies the code from the authenticator app. Moves the session to `AAL2`. |
63
+ | `mfaFactorUnenroll` | Removes an authenticator. Needs `AAL2` for a verified factor. |
57
64
  | `apiKeyCreate` | Creates an API key. Only a user with a session can call it. |
58
65
  | `apiKeyRevoke` | Invalidates a key immediately. |
59
66
 
@@ -205,7 +205,11 @@ Some mutations do not require organization-level scopes because they operate on
205
205
  | `passwordResetSend` / `passwordReset` | No authentication required |
206
206
  | `passwordUpdate` | Authenticated session (no specific scope) |
207
207
  | `organizationInvitationAccept` / `organizationInvitationDecline` | Authenticated session (no specific scope) |
208
- | `mfaFactorEnroll` / `mfaChallengeSend` / `mfaChallengeVerify` / `mfaFactorUnenroll` | Authenticated session (no specific scope) |
208
+ | `mfaFactorEnroll` / `mfaChallengeCreate` / `mfaChallengeVerify` / `mfaFactorUnenroll` | Authenticated session (no specific scope). Exempt from the MFA requirement, so a user can add a factor and verify it. |
209
+
210
+ `userMfaReset` needs the root-level scope `users/reset_mfa` (`USERS_RESET_MFA`). The
211
+ mutation removes every authenticator of a user and signs the user out everywhere, so
212
+ their next sign-in starts at the setup.
209
213
 
210
214
  ## Read your scopes
211
215
 
@@ -54,6 +54,7 @@ operation, or MFA. They match the `AuthErrorCode` enum in the schema.
54
54
  | `TOKEN_EXPIRED` | The token expired. Ask for a new one. |
55
55
  | `MFA_CHALLENGE_FAILED` | The MFA verification code is not correct. |
56
56
  | `MFA_FACTOR_NOT_FOUND` | That MFA factor is not there, or it belongs to another caller. |
57
+ | `MFA_REQUIRED` | The user must verify a code from an authenticator app before this operation. Complete the challenge, then use the returned session. |
57
58
  | `OAUTH_PROVIDER_ERROR` | The exchange with the OAuth provider, such as Google, failed. |
58
59
  | `RATE_LIMITED` | Too many requests in a short period. Wait, then try again. |
59
60
 
@@ -63,9 +64,7 @@ operation, or MFA. They match the `AuthErrorCode` enum in the schema.
63
64
  {
64
65
  "data": {
65
66
  "sessionCreate": {
66
- "session": null,
67
- "requiresMfa": false,
68
- "mfaFactors": null
67
+ "session": null
69
68
  }
70
69
  },
71
70
  "errors": [
@@ -128,6 +128,7 @@ For a Shopify store, the `session` context also holds `shopify_client_id`,
128
128
  | `page_view` | The visitor opens a page. | `app`, `feature_list`, `session` | `experience`, `product_list`, `step` |
129
129
  | `identify` | The SDK connects the session to a known person or store client. | `app`, `session` | `experience`, `feature_list`, `step` |
130
130
  | `lead_capture` | The visitor gives contact details. | `feature_list`, `session` | `experience`, `step` |
131
+ | `lead_created` | The Picked API records the lead from a capture. | `app`, `session` | None. |
131
132
  | `modal_open` | A modal opens. | `feature_list`, `modal`, `session` | `experience`, `step` |
132
133
  | `modal_close` | A modal closes. | `feature_list`, `modal`, `session` | `experience`, `step` |
133
134
 
@@ -193,15 +194,13 @@ the analytics plugin is true. The option is off by default.
193
194
 
194
195
  ### Interactions with no listener
195
196
 
196
- The SDK declares these four types and sends none of them, because it binds no listener for
197
+ The SDK declares these two types and sends none of them, because it binds no listener for
197
198
  them. A query on one of them gives an empty result set.
198
199
 
199
200
  | Interaction type | When it fires | Always attached | Attached when available |
200
201
  | --- | --- | --- | --- |
201
202
  | `back` | Nothing sends it. | `feature_list`, `session` | `experience`, `step` |
202
203
  | `forward` | Nothing sends it. | `feature_list`, `session` | `experience`, `step` |
203
- | `video_play` | Nothing sends it. | `element`, `feature_list`, `session` | `experience`, `step` |
204
- | `video_pause` | Nothing sends it. | `element`, `feature_list`, `session` | `experience`, `step` |
205
204
 
206
205
  ## Schemas
207
206
 
@@ -90,8 +90,11 @@ mutation {
90
90
  accessToken
91
91
  refreshToken
92
92
  expiresAt
93
+ aal
94
+ user {
95
+ mfa { required enrolled }
96
+ }
93
97
  }
94
- requiresMfa
95
98
  }
96
99
  }
97
100
  ```
@@ -106,16 +109,20 @@ A successful response returns a `Session` with an `accessToken` and a
106
109
  "session": {
107
110
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
108
111
  "refreshToken": "v1.refresh-token...",
109
- "expiresAt": "2025-01-15T12:00:00Z"
110
- },
111
- "requiresMfa": false
112
+ "expiresAt": "2025-01-15T12:00:00Z",
113
+ "aal": "AAL1",
114
+ "user": {
115
+ "mfa": { "required": false, "enrolled": false }
116
+ }
117
+ }
112
118
  }
113
119
  }
114
120
  }
115
121
  ```
116
122
 
117
- When `requiresMfa` is `true`, you must complete the MFA challenge before you receive
118
- a session. See the [Authentication](/reference/management-api/authentication) guide.
123
+ When `required` is `true`, the token can only read `user` and call the MFA mutations
124
+ until you verify a code from an authenticator app. See
125
+ [How to set up MFA](/how-to/set-up-mfa).
119
126
 
120
127
  For a server-to-server integration, use
121
128
  [API key authentication](/how-to/authenticate-with-an-api-key) and not a