@picked/client 0.0.1-dev.1777616114652 → 0.0.1-dev.1777867216429

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 (56) hide show
  1. package/dist/{api-Dn57QBPm.js → api-Xp22knXn.js} +1 -1
  2. package/dist/{api-BryLRI2Y.cjs → api-qBG8rsTJ.cjs} +1 -1
  3. package/dist/cjs/form/index.js +1 -1
  4. package/dist/cjs/index.js +2 -2
  5. package/dist/cjs/plugins/create-experience/index.js +1 -1
  6. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  7. package/dist/cjs/plugins/experience-browser-storage/index.js +1 -1
  8. package/dist/cjs/plugins/history-browser-storage/index.js +1 -1
  9. package/dist/cjs/plugins/responses-browser-storage/index.js +1 -1
  10. package/dist/cjs/server/index.js +1 -1
  11. package/dist/docs/api/authentication.md +376 -0
  12. package/dist/docs/api/authorization-and-scopes.md +262 -0
  13. package/dist/docs/api/error-handling.md +211 -0
  14. package/dist/docs/api/index.md +172 -11
  15. package/dist/docs/api/pagination.md +176 -0
  16. package/dist/docs/image-optimisation-service.md +72 -0
  17. package/dist/docs/sdk/plugins/dynamic-experiences.md +2 -2
  18. package/dist/docs/workspaces.mdx +60 -1
  19. package/dist/esm/form/index.js +40 -33
  20. package/dist/esm/index.js +295 -170
  21. package/dist/esm/plugins/create-experience/index.js +1 -1
  22. package/dist/esm/plugins/dynamic-experiences/index.js +1 -1
  23. package/dist/esm/plugins/experience-browser-storage/index.js +1 -1
  24. package/dist/esm/plugins/history-browser-storage/index.js +1 -1
  25. package/dist/esm/plugins/responses-browser-storage/index.js +1 -1
  26. package/dist/esm/server/index.js +1 -1
  27. package/dist/experience-instance-BWniatU5.cjs +1 -0
  28. package/dist/experience-instance-C4UJM092.js +506 -0
  29. package/dist/types/entities/experience-instance/experience-instance.d.ts.map +1 -1
  30. package/dist/types/entities/history/history.d.ts +9 -0
  31. package/dist/types/entities/history/history.d.ts.map +1 -1
  32. package/dist/types/form/create-form.d.ts +55 -0
  33. package/dist/types/form/create-form.d.ts.map +1 -1
  34. package/package.json +1 -11
  35. package/dist/cjs/plugins/dom-observer/index.js +0 -1
  36. package/dist/docs/reference/index.md +0 -11
  37. package/dist/docs/tooling/index.md +0 -19
  38. package/dist/esm/plugins/dom-observer/index.js +0 -81
  39. package/dist/experience-BhteyYBl.cjs +0 -1
  40. package/dist/experience-CoMdlvyn.js +0 -136
  41. package/dist/experience-instance-CZDcicPj.cjs +0 -1
  42. package/dist/experience-instance-IkIw31Cg.js +0 -387
  43. package/dist/nodes-ChVU1VZO.js +0 -101
  44. package/dist/nodes-DDDs-rC2.cjs +0 -1
  45. package/dist/types/plugins/dom-observer/index.d.ts +0 -3
  46. package/dist/types/plugins/dom-observer/index.d.ts.map +0 -1
  47. package/dist/types/plugins/dom-observer/plugin.d.ts +0 -39
  48. package/dist/types/plugins/dom-observer/plugin.d.ts.map +0 -1
  49. /package/dist/docs/{tooling/chrome-extension.md → chrome-extension.md} +0 -0
  50. /package/dist/docs/{tooling/gtm-template.md → gtm-template.md} +0 -0
  51. /package/dist/docs/{tooling/mcp.md → mcp.md} +0 -0
  52. /package/dist/docs/{tooling/vite-plugin.md → vite-plugin.md} +0 -0
  53. /package/dist/{id-Bbodazn8.cjs → id-Bv44ilEo.cjs} +0 -0
  54. /package/dist/{id-B-N2zNbS.js → id-CRqy981z.js} +0 -0
  55. /package/dist/{storage-hOHCCXvP.js → storage-6Xk0uAxB.js} +0 -0
  56. /package/dist/{storage-BXSlLTRa.cjs → storage-B6lQtc4-.cjs} +0 -0
@@ -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`.
@@ -0,0 +1,211 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Error handling
6
+
7
+ The Picked API returns errors using the standard GraphQL error format. This guide covers the error response shape, all error codes, and practical strategies for handling errors.
8
+
9
+ ## Error response shape
10
+
11
+ When an operation fails, the API returns an `errors` array in the response body. Each error object contains:
12
+
13
+ ```json
14
+ {
15
+ "data": null,
16
+ "errors": [
17
+ {
18
+ "message": "A human-readable description of the error",
19
+ "path": ["mutationName"],
20
+ "extensions": {
21
+ "code": "ERROR_CODE"
22
+ }
23
+ }
24
+ ]
25
+ }
26
+ ```
27
+
28
+ | Field | Description |
29
+ |-------|-------------|
30
+ | `message` | A human-readable error description. Suitable for logging but not for programmatic handling. |
31
+ | `path` | The path to the field in the query that caused the error. |
32
+ | `extensions.code` | A machine-readable error code. Use this for programmatic error handling. |
33
+
34
+ Multiple errors may appear in a single response. The `data` field may be `null` if the entire operation failed, or it may contain partial data if only some fields failed.
35
+
36
+ ## Authentication error codes
37
+
38
+ These errors relate to session creation, account management, OAuth, magic links, password operations, and MFA. They map to the `AuthErrorCode` enum in the schema.
39
+
40
+ | Code | Description |
41
+ |------|-------------|
42
+ | `INVALID_CREDENTIALS` | The email/password combination is incorrect. |
43
+ | `EMAIL_NOT_CONFIRMED` | The account exists but the email address has not been verified. See [Authentication](/api/authentication#email-verification). |
44
+ | `USER_NOT_FOUND` | No account exists with the provided email address. |
45
+ | `USER_ALREADY_EXISTS` | An account with this email already exists. Returned during account creation. |
46
+ | `WEAK_PASSWORD` | The provided password does not meet strength requirements. |
47
+ | `INVALID_TOKEN` | The token (verification, reset, or magic link) is invalid or malformed. |
48
+ | `TOKEN_EXPIRED` | The token has expired. The caller should request a new one. |
49
+ | `MFA_CHALLENGE_FAILED` | The MFA verification code is incorrect. |
50
+ | `MFA_FACTOR_NOT_FOUND` | The specified MFA factor does not exist or does not belong to the caller. |
51
+ | `OAUTH_PROVIDER_ERROR` | An error occurred communicating with the OAuth provider (e.g., Google). |
52
+ | `RATE_LIMITED` | Too many requests in a short period. The caller should wait and retry. |
53
+
54
+ ### Example: invalid credentials
55
+
56
+ ```json
57
+ {
58
+ "data": {
59
+ "sessionCreate": {
60
+ "session": null,
61
+ "requiresMfa": false,
62
+ "mfaFactors": null
63
+ }
64
+ },
65
+ "errors": [
66
+ {
67
+ "message": "Invalid credentials",
68
+ "path": ["sessionCreate"],
69
+ "extensions": {
70
+ "code": "INVALID_CREDENTIALS"
71
+ }
72
+ }
73
+ ]
74
+ }
75
+ ```
76
+
77
+ ## Authorization error codes
78
+
79
+ Authorization errors occur when the caller lacks the required [scope](./authorization-and-scopes.md) for an operation.
80
+
81
+ | Code | Description |
82
+ |------|-------------|
83
+ | `UNAUTHORIZED` | No valid authentication was provided. The request is missing the `Authorization` header or the `x-api-key` header, or the token/key is invalid. |
84
+ | `FORBIDDEN` | The caller is authenticated but does not have the required scope for the operation. |
85
+
86
+ ### Example: insufficient scope
87
+
88
+ A caller with only `organization/read` attempting to delete an organization receives:
89
+
90
+ ```json
91
+ {
92
+ "data": {
93
+ "organizationDelete": null
94
+ },
95
+ "errors": [
96
+ {
97
+ "message": "Insufficient permissions",
98
+ "path": ["organizationDelete"],
99
+ "extensions": {
100
+ "code": "FORBIDDEN"
101
+ }
102
+ }
103
+ ]
104
+ }
105
+ ```
106
+
107
+ ## Organization, member, and invitation error codes
108
+
109
+ These errors relate to organization management, member operations, and invitation workflows. They map to the `ErrorCode` enum in the schema.
110
+
111
+ ### Organization errors
112
+
113
+ | Code | Description |
114
+ |------|-------------|
115
+ | `ORGANIZATION_NOT_FOUND` | The specified organization does not exist or the caller does not have access to it. |
116
+ | `ORGANIZATION_ALREADY_EXISTS` | An organization with this name already exists. |
117
+ | `SLUG_ALREADY_EXISTS` | The requested slug is already in use by another organization. |
118
+ | `INVALID_SLUG` | The slug contains invalid characters. Slugs must be lowercase alphanumeric with hyphens. |
119
+
120
+ ### Member errors
121
+
122
+ | Code | Description |
123
+ |------|-------------|
124
+ | `MEMBER_NOT_FOUND` | The specified user is not a member of the organization. |
125
+ | `MEMBER_ALREADY_EXISTS` | The user is already a member of the organization. |
126
+ | `CANNOT_REMOVE_PRIMARY_OWNER` | The primary owner of an organization cannot be removed. |
127
+ | `CANNOT_CHANGE_OWN_ROLE` | A member cannot change their own role. Another owner must perform this action. |
128
+
129
+ ### Invitation errors
130
+
131
+ | Code | Description |
132
+ |------|-------------|
133
+ | `INVITATION_NOT_FOUND` | The specified invitation does not exist. |
134
+ | `INVITATION_EXPIRED` | The invitation has expired and can no longer be accepted. |
135
+ | `INVITATION_ALREADY_ACCEPTED` | The invitation has already been accepted. |
136
+
137
+ ### Project errors
138
+
139
+ | Code | Description |
140
+ |------|-------------|
141
+ | `PROJECT_NOT_FOUND` | The specified project does not exist or the caller does not have access. |
142
+
143
+ ### General errors
144
+
145
+ | Code | Description |
146
+ |------|-------------|
147
+ | `VALIDATION_ERROR` | The input failed validation. Check the `message` field for details about which field is invalid. |
148
+ | `INTERNAL_ERROR` | An unexpected server error occurred. Retry the request or contact support if the issue persists. |
149
+
150
+ ## Handling errors in practice
151
+
152
+ ### Check for errors first
153
+
154
+ Every response should be checked for the presence of an `errors` array before processing `data`:
155
+
156
+ ```javascript
157
+ const response = await fetch('/graphql', {
158
+ method: 'POST',
159
+ headers: {
160
+ 'Content-Type': 'application/json',
161
+ 'Authorization': `Bearer ${accessToken}`,
162
+ },
163
+ body: JSON.stringify({ query, variables }),
164
+ });
165
+
166
+ const result = await response.json();
167
+
168
+ if (result.errors) {
169
+ for (const error of result.errors) {
170
+ const code = error.extensions?.code;
171
+
172
+ switch (code) {
173
+ case 'UNAUTHORIZED':
174
+ // Redirect to login
175
+ break;
176
+ case 'FORBIDDEN':
177
+ // Show a permissions error
178
+ break;
179
+ case 'RATE_LIMITED':
180
+ // Wait and retry
181
+ break;
182
+ default:
183
+ // Log or display the error message
184
+ console.error(error.message);
185
+ }
186
+ }
187
+ }
188
+ ```
189
+
190
+ ### Refresh expired tokens
191
+
192
+ If a request returns `UNAUTHORIZED` and the caller has a refresh token, attempt a [session refresh](/api/authentication#session-refresh) before treating it as a login failure:
193
+
194
+ ```javascript
195
+ if (code === 'UNAUTHORIZED' && refreshToken) {
196
+ const refreshResult = await sessionRefresh(refreshToken);
197
+ if (refreshResult.session) {
198
+ // Retry the original request with the new access token
199
+ } else {
200
+ // Redirect to login
201
+ }
202
+ }
203
+ ```
204
+
205
+ ### Handle partial data
206
+
207
+ GraphQL can return partial data alongside errors. For example, a query that fetches an organization and its members might succeed for the organization but fail for the members if the caller lacks `organization_members/read`. In this case, the response contains both `data` (with the organization) and `errors` (for the members field). Always check both.
208
+
209
+ ### Retry transient failures
210
+
211
+ `INTERNAL_ERROR` and `RATE_LIMITED` are transient. For `RATE_LIMITED`, implement exponential backoff before retrying. For `INTERNAL_ERROR`, a single retry is usually sufficient.
@@ -2,31 +2,192 @@
2
2
  layout: docs
3
3
  ---
4
4
 
5
- # API
5
+ # Getting started
6
6
 
7
- The Picked API is a GraphQL API that provides programmatic access to all platform features.
7
+ This guide walks you through your first interactions with the Picked GraphQL API, from sending a simple ping to making authenticated queries and mutations.
8
8
 
9
9
  ## Playground
10
10
 
11
- Explore the API and run queries interactively using the [GraphQL Playground](https://api.picked.app/api/graphql).
11
+ Explore the API and run queries interactively in the [GraphQL Playground](https://api.picked.app/api/graphql).
12
12
 
13
- ## Authentication
13
+ ## API endpoint
14
14
 
15
- Authenticate requests by including an API key in the `Authorization` header.
15
+ All requests go to a single endpoint:
16
16
 
17
17
  ```
18
- Authorization: Bearer your-api-key
18
+ POST https://api.picked.app/api/graphql
19
19
  ```
20
20
 
21
- See [API Keys](/api-keys) for how to create and manage keys.
21
+ Every request must use the `POST` method with a JSON body containing a `query` field and an optional `variables` field:
22
22
 
23
- ## Making requests
23
+ ```json
24
+ {
25
+ "query": "query { ... }",
26
+ "variables": { }
27
+ }
28
+ ```
24
29
 
25
- Send a POST request to the GraphQL endpoint with your query in the request body.
30
+ Set the `Content-Type` header to `application/json` on every request.
26
31
 
27
32
  ```bash
28
33
  curl -X POST https://api.picked.app/api/graphql \
29
34
  -H "Content-Type: application/json" \
30
- -H "Authorization: Bearer your-api-key" \
31
- -d '{"query": "{ viewer { id email } }"}'
35
+ -d '{"query": "{ ping }"}'
36
+ ```
37
+
38
+ ## Ping the API
39
+
40
+ Verify that the API is reachable with the `ping` query. This query does not require authentication.
41
+
42
+ ```graphql
43
+ query {
44
+ ping
45
+ }
46
+ ```
47
+
48
+ Response:
49
+
50
+ ```json
51
+ {
52
+ "data": {
53
+ "ping": "pong"
54
+ }
55
+ }
56
+ ```
57
+
58
+ If you receive this response, the API is up and ready.
59
+
60
+ ## Authenticate
61
+
62
+ Most operations require an access token. You obtain one by creating a session with your email and password.
63
+
64
+ ```graphql
65
+ mutation {
66
+ sessionCreate(input: {
67
+ email: "you@example.com"
68
+ password: "your-password"
69
+ }) {
70
+ session {
71
+ accessToken
72
+ refreshToken
73
+ expiresAt
74
+ }
75
+ requiresMfa
76
+ }
77
+ }
78
+ ```
79
+
80
+ A successful response returns a `Session` containing an `accessToken` and a `refreshToken`:
81
+
82
+ ```json
83
+ {
84
+ "data": {
85
+ "sessionCreate": {
86
+ "session": {
87
+ "accessToken": "eyJhbGciOiJIUzI1NiIs...",
88
+ "refreshToken": "v1.refresh-token...",
89
+ "expiresAt": "2025-01-15T12:00:00Z"
90
+ },
91
+ "requiresMfa": false
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ If `requiresMfa` is `true`, you need to complete the MFA challenge before receiving a session. See the [Authentication](./authentication.md) guide for details.
98
+
99
+ For server-to-server integrations, prefer [API key authentication](/api/authentication#api-key-authentication) over session tokens.
100
+
101
+ ## Make an authenticated query
102
+
103
+ Pass the access token in the `Authorization` header as a Bearer token:
104
+
105
+ ```
106
+ Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
107
+ ```
108
+
109
+ Now you can query resources that require authentication. For example, fetch your user profile:
110
+
111
+ ```graphql
112
+ query {
113
+ user {
114
+ id
115
+ email
116
+ createdAt
117
+ organizations(first: 5) {
118
+ edges {
119
+ node {
120
+ id
121
+ name
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
32
127
  ```
128
+
129
+ ## Make a mutation
130
+
131
+ Mutations follow the same pattern: send a `POST` request with the `Authorization` header. For example, create an organization:
132
+
133
+ ```graphql
134
+ mutation {
135
+ organizationCreate(input: {
136
+ name: "My Company"
137
+ slug: "my-company"
138
+ }) {
139
+ organization {
140
+ id
141
+ name
142
+ slug
143
+ }
144
+ }
145
+ }
146
+ ```
147
+
148
+ Response:
149
+
150
+ ```json
151
+ {
152
+ "data": {
153
+ "organizationCreate": {
154
+ "organization": {
155
+ "id": "org_abc123",
156
+ "name": "My Company",
157
+ "slug": "my-company"
158
+ }
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ Every mutation accepts an `input` argument and returns a payload object. The payload contains the created or updated resource on success, or errors in the top-level `errors` array on failure.
165
+
166
+ ## Error handling basics
167
+
168
+ When something goes wrong, the API returns errors in the standard GraphQL `errors` array:
169
+
170
+ ```json
171
+ {
172
+ "data": null,
173
+ "errors": [
174
+ {
175
+ "message": "Invalid credentials",
176
+ "extensions": {
177
+ "code": "INVALID_CREDENTIALS"
178
+ }
179
+ }
180
+ ]
181
+ }
182
+ ```
183
+
184
+ Check the `extensions.code` field for a machine-readable error code you can use to handle specific failures programmatically. See the [Error Handling](./error-handling.md) guide for the full list of error codes.
185
+
186
+ ## Next steps
187
+
188
+ Now that you can authenticate and make requests, explore these guides to go deeper:
189
+
190
+ - [Authentication](./authentication.md) — email/password, OAuth, magic links, MFA, API keys, and session management
191
+ - [Authorization and Scopes](./authorization-and-scopes.md) — understand what each role can access and how scopes control permissions
192
+ - [Pagination](./pagination.md) — navigate large result sets with cursor-based pagination
193
+ - [Error Handling](./error-handling.md) — handle every error code the API can return