querri 1.0.8__tar.gz → 2.0.0__tar.gz

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 (115) hide show
  1. querri-2.0.0/CHANGELOG.md +89 -0
  2. querri-2.0.0/LICENSE +21 -0
  3. {querri-1.0.8 → querri-2.0.0}/PKG-INFO +48 -5
  4. {querri-1.0.8 → querri-2.0.0}/README.md +45 -3
  5. querri-2.0.0/docs/MIGRATION.md +120 -0
  6. {querri-1.0.8 → querri-2.0.0}/docs/server-sdk.md +124 -33
  7. {querri-1.0.8 → querri-2.0.0}/examples/11_user_scoped_client.py +5 -3
  8. {querri-1.0.8 → querri-2.0.0}/examples/web-embed/index.html +18 -14
  9. {querri-1.0.8 → querri-2.0.0}/examples/web-embed/server.py +6 -4
  10. {querri-1.0.8 → querri-2.0.0}/querri/__init__.py +2 -0
  11. {querri-1.0.8 → querri-2.0.0}/querri/_client.py +8 -8
  12. {querri-1.0.8 → querri-2.0.0}/querri/_convenience.py +20 -0
  13. {querri-1.0.8 → querri-2.0.0}/querri/_exceptions.py +21 -0
  14. {querri-1.0.8 → querri-2.0.0}/querri/_pagination.py +28 -12
  15. {querri-1.0.8 → querri-2.0.0}/querri/_user_client.py +52 -21
  16. querri-2.0.0/querri/_version.py +1 -0
  17. {querri-1.0.8 → querri-2.0.0}/querri/cli/_output.py +33 -0
  18. {querri-1.0.8 → querri-2.0.0}/querri/cli/audit.py +9 -2
  19. {querri-1.0.8 → querri-2.0.0}/querri/cli/embed.py +26 -0
  20. {querri-1.0.8 → querri-2.0.0}/querri/cli/files.py +4 -2
  21. {querri-1.0.8 → querri-2.0.0}/querri/cli/keys.py +10 -2
  22. {querri-1.0.8 → querri-2.0.0}/querri/cli/sharing.py +10 -3
  23. {querri-1.0.8 → querri-2.0.0}/querri/cli/sources.py +12 -4
  24. {querri-1.0.8 → querri-2.0.0}/querri/cli/views.py +4 -2
  25. {querri-1.0.8 → querri-2.0.0}/querri/resources/audit.py +13 -17
  26. {querri-1.0.8 → querri-2.0.0}/querri/resources/embed.py +160 -24
  27. {querri-1.0.8 → querri-2.0.0}/querri/resources/files.py +34 -13
  28. {querri-1.0.8 → querri-2.0.0}/querri/resources/keys.py +35 -12
  29. {querri-1.0.8 → querri-2.0.0}/querri/resources/sources.py +63 -32
  30. {querri-1.0.8 → querri-2.0.0}/querri/resources/views.py +33 -12
  31. {querri-1.0.8 → querri-2.0.0}/skills/querri-cli/SKILL.md +2 -1
  32. {querri-1.0.8 → querri-2.0.0}/tests/test_cli_commands.py +143 -15
  33. {querri-1.0.8 → querri-2.0.0}/tests/test_integration.py +1 -1
  34. {querri-1.0.8 → querri-2.0.0}/tests/test_new_features.py +36 -3
  35. {querri-1.0.8 → querri-2.0.0}/tests/test_pagination.py +36 -0
  36. {querri-1.0.8 → querri-2.0.0}/tests/test_resources.py +302 -54
  37. querri-2.0.0/uv.lock +936 -0
  38. querri-1.0.8/querri/_version.py +0 -1
  39. {querri-1.0.8 → querri-2.0.0}/.github/CONTRIBUTING.md +0 -0
  40. {querri-1.0.8 → querri-2.0.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  41. {querri-1.0.8 → querri-2.0.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  42. {querri-1.0.8 → querri-2.0.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  43. {querri-1.0.8 → querri-2.0.0}/.github/SECURITY.md +0 -0
  44. {querri-1.0.8 → querri-2.0.0}/.github/workflows/ci.yml +0 -0
  45. {querri-1.0.8 → querri-2.0.0}/.github/workflows/release.yml +0 -0
  46. {querri-1.0.8 → querri-2.0.0}/.gitignore +0 -0
  47. {querri-1.0.8 → querri-2.0.0}/CLAUDE.md +0 -0
  48. {querri-1.0.8 → querri-2.0.0}/TODO.md +0 -0
  49. {querri-1.0.8 → querri-2.0.0}/examples/01_quick_start.py +0 -0
  50. {querri-1.0.8 → querri-2.0.0}/examples/02_user_management.py +0 -0
  51. {querri-1.0.8 → querri-2.0.0}/examples/03_access_policies.py +0 -0
  52. {querri-1.0.8 → querri-2.0.0}/examples/04_embed_session.py +0 -0
  53. {querri-1.0.8 → querri-2.0.0}/examples/05_get_session.py +0 -0
  54. {querri-1.0.8 → querri-2.0.0}/examples/06_projects.py +0 -0
  55. {querri-1.0.8 → querri-2.0.0}/examples/07_dashboards.py +0 -0
  56. {querri-1.0.8 → querri-2.0.0}/examples/08_chat_streaming.py +0 -0
  57. {querri-1.0.8 → querri-2.0.0}/examples/09_async_client.py +0 -0
  58. {querri-1.0.8 → querri-2.0.0}/examples/10_error_handling.py +0 -0
  59. {querri-1.0.8 → querri-2.0.0}/examples/12_data_operations.py +0 -0
  60. {querri-1.0.8 → querri-2.0.0}/pyproject.toml +0 -0
  61. {querri-1.0.8 → querri-2.0.0}/querri/_auth.py +0 -0
  62. {querri-1.0.8 → querri-2.0.0}/querri/_base_client.py +0 -0
  63. {querri-1.0.8 → querri-2.0.0}/querri/_config.py +0 -0
  64. {querri-1.0.8 → querri-2.0.0}/querri/_streaming.py +0 -0
  65. {querri-1.0.8 → querri-2.0.0}/querri/cli/__init__.py +0 -0
  66. {querri-1.0.8 → querri-2.0.0}/querri/cli/_app.py +0 -0
  67. {querri-1.0.8 → querri-2.0.0}/querri/cli/_context.py +0 -0
  68. {querri-1.0.8 → querri-2.0.0}/querri/cli/_image.py +0 -0
  69. {querri-1.0.8 → querri-2.0.0}/querri/cli/auth.py +0 -0
  70. {querri-1.0.8 → querri-2.0.0}/querri/cli/chat.py +0 -0
  71. {querri-1.0.8 → querri-2.0.0}/querri/cli/chats.py +0 -0
  72. {querri-1.0.8 → querri-2.0.0}/querri/cli/dashboards.py +0 -0
  73. {querri-1.0.8 → querri-2.0.0}/querri/cli/policies.py +0 -0
  74. {querri-1.0.8 → querri-2.0.0}/querri/cli/projects.py +0 -0
  75. {querri-1.0.8 → querri-2.0.0}/querri/cli/steps.py +0 -0
  76. {querri-1.0.8 → querri-2.0.0}/querri/cli/usage.py +0 -0
  77. {querri-1.0.8 → querri-2.0.0}/querri/cli/users.py +0 -0
  78. {querri-1.0.8 → querri-2.0.0}/querri/cli/whoami.py +0 -0
  79. {querri-1.0.8 → querri-2.0.0}/querri/py.typed +0 -0
  80. {querri-1.0.8 → querri-2.0.0}/querri/resources/__init__.py +0 -0
  81. {querri-1.0.8 → querri-2.0.0}/querri/resources/dashboards.py +0 -0
  82. {querri-1.0.8 → querri-2.0.0}/querri/resources/policies.py +0 -0
  83. {querri-1.0.8 → querri-2.0.0}/querri/resources/projects.py +0 -0
  84. {querri-1.0.8 → querri-2.0.0}/querri/resources/sharing.py +0 -0
  85. {querri-1.0.8 → querri-2.0.0}/querri/resources/usage.py +0 -0
  86. {querri-1.0.8 → querri-2.0.0}/querri/resources/users.py +0 -0
  87. {querri-1.0.8 → querri-2.0.0}/querri/types/__init__.py +0 -0
  88. {querri-1.0.8 → querri-2.0.0}/querri/types/audit.py +0 -0
  89. {querri-1.0.8 → querri-2.0.0}/querri/types/chat.py +0 -0
  90. {querri-1.0.8 → querri-2.0.0}/querri/types/dashboard.py +0 -0
  91. {querri-1.0.8 → querri-2.0.0}/querri/types/data.py +0 -0
  92. {querri-1.0.8 → querri-2.0.0}/querri/types/embed.py +0 -0
  93. {querri-1.0.8 → querri-2.0.0}/querri/types/file.py +0 -0
  94. {querri-1.0.8 → querri-2.0.0}/querri/types/key.py +0 -0
  95. {querri-1.0.8 → querri-2.0.0}/querri/types/policy.py +0 -0
  96. {querri-1.0.8 → querri-2.0.0}/querri/types/project.py +0 -0
  97. {querri-1.0.8 → querri-2.0.0}/querri/types/sharing.py +0 -0
  98. {querri-1.0.8 → querri-2.0.0}/querri/types/usage.py +0 -0
  99. {querri-1.0.8 → querri-2.0.0}/querri/types/user.py +0 -0
  100. {querri-1.0.8 → querri-2.0.0}/scripts/release.sh +0 -0
  101. {querri-1.0.8 → querri-2.0.0}/skills/README.md +0 -0
  102. {querri-1.0.8 → querri-2.0.0}/skills/querri-cli/references/json-shapes.md +0 -0
  103. {querri-1.0.8 → querri-2.0.0}/skills/querri-cli/references/troubleshooting.md +0 -0
  104. {querri-1.0.8 → querri-2.0.0}/tests/__init__.py +0 -0
  105. {querri-1.0.8 → querri-2.0.0}/tests/conftest.py +0 -0
  106. {querri-1.0.8 → querri-2.0.0}/tests/rls-integration-results.md +0 -0
  107. {querri-1.0.8 → querri-2.0.0}/tests/test_cli.py +0 -0
  108. {querri-1.0.8 → querri-2.0.0}/tests/test_client.py +0 -0
  109. {querri-1.0.8 → querri-2.0.0}/tests/test_config.py +0 -0
  110. {querri-1.0.8 → querri-2.0.0}/tests/test_convenience.py +0 -0
  111. {querri-1.0.8 → querri-2.0.0}/tests/test_exceptions.py +0 -0
  112. {querri-1.0.8 → querri-2.0.0}/tests/test_rls_integration.py +0 -0
  113. {querri-1.0.8 → querri-2.0.0}/tests/test_security.py +0 -0
  114. {querri-1.0.8 → querri-2.0.0}/tests/test_streaming.py +0 -0
  115. {querri-1.0.8 → querri-2.0.0}/tests/test_streaming_v2.py +0 -0
@@ -0,0 +1,89 @@
1
+ # Changelog
2
+
3
+ ## 2.0.0 (2026-08-31)
4
+
5
+ Migration guide with before/after snippets: [docs/MIGRATION.md](docs/MIGRATION.md).
6
+
7
+ ### Breaking
8
+
9
+ - **`source_scope` removed from `client.embed.create_session()`** (sync and
10
+ async). It was never enforced by the server — a silently-ignored parameter
11
+ that *looked* like access control. Passing it now raises `TypeError`. Scope
12
+ sessions with access policies instead: `client.embed.get_session(access=...)`.
13
+ - **`as_user()` / `UserQuerri` rebased from `/api` to `/api/v1`.** Embed
14
+ sessions (`X-Embed-Session`) are the public API's highest-priority
15
+ credential, so user-scoped clients now call the same public API paths as
16
+ the admin client.
17
+ - **`user_client.dashboards` is read-only.** Embed sessions exclude the
18
+ `admin:dashboards:write` scope server-side; the user-scoped surface now
19
+ exposes only `list()`, `get()`, and `refresh_status()` — `create`,
20
+ `update`, `delete`, and `refresh` no longer exist there (they always
21
+ belonged to the admin client).
22
+ - There is no `user_client.embed`: embed sessions exclude
23
+ `embed:session:create` server-side (a session cannot mint sessions).
24
+ The accessor never existed in the SDK; the contract is now documented
25
+ and tested.
26
+ - **`ttl` is validated client-side**: `create_session()` and
27
+ `get_session()` raise `ValueError` for TTLs outside [900, 86400] seconds
28
+ instead of sending a value the server would silently clamp.
29
+
30
+ ### Added
31
+
32
+ - `client.embed.get_ui_config(org)` — fetches the public, unauthenticated
33
+ `GET {host}/api/embed/ui-config?org=...` (main app path, not `/api/v1`)
34
+ and returns the raw `{chrome, theme, privacy}` dict.
35
+ - `querri session ui-config --org <id>` CLI command for the same.
36
+ - `OriginRequiredError` (subclass of `ValidationError`), raised for
37
+ `400 origin_required` with a message explaining the org's embed-domain
38
+ allowlist and how to pass `origin`.
39
+ - `querri share source org` gained `--disable` to turn org-wide sharing
40
+ off (the server contract always supported `enabled: false`).
41
+ - `querri keys create` interactive scope picker now includes the
42
+ `admin:skills:*`, `admin:library:*` scopes and `*` (superadmin).
43
+
44
+ ### Fixed
45
+
46
+ - `client.embed.revoke_user_sessions()` no longer stops at the first
47
+ listing page: the server caps listings at 200 with no cursor, so it now
48
+ loops revoke-and-relist until a listing shows no sessions for the user
49
+ (bounded at 50 passes), and tolerates a session expiring between list
50
+ and delete. Still best-effort — with more than 200 concurrent sessions
51
+ org-wide the listing is a sample and stragglers can survive; do not
52
+ treat a return as proof every session is gone.
53
+
54
+ ## 1.1.1 (2026-08-31)
55
+
56
+ ### Fixed
57
+
58
+ - **Web-embed example** (`examples/web-embed/server.py`): three dispatched
59
+ actions called keyword-only SDK methods positionally, raising `TypeError`
60
+ (returned as HTTP 500): `embed.refreshSession`, `users.getOrCreate`, and
61
+ `policies.resolve`. All now pass keyword arguments.
62
+ - **Web-embed example** (`examples/web-embed/index.html`): the SDK Explorer's
63
+ "Data" group sent `data.*` action names the server never dispatched — every
64
+ action in the group returned "Unknown action". Renamed to the real
65
+ `sources.*` actions.
66
+ - **Docs**: `docs/server-sdk.md` and `README.md` showed `create_session`,
67
+ `refresh_session`, and `get_session` with positional parameters; all three
68
+ are keyword-only (`create_session(*, user_id=..., origin=..., ttl=...)`,
69
+ `refresh_session(session_token=...)`). Snippets updated to keyword form.
70
+ - **Browser embed loading**: examples and docs now load the embed script from
71
+ the product — `<script src="{serverUrl}/sdk/querri-embed.js">` — instead of
72
+ an unverifiable CDN/npm IIFE path. The web-embed example injects the script
73
+ after fetching `serverUrl` from its `/api/config` endpoint.
74
+ - **Embed chrome options**: examples use the v2 vocabulary
75
+ (`chrome: { rail: { show: true }, header: { show: true } }` — `rail`
76
+ replaces `sidebar`).
77
+ - **Origin allowlist guidance**: create-session docs and examples now pass
78
+ `origin` and document the enforcement behavior — when the organization
79
+ configures allowed embed domains, session creation without an origin fails
80
+ with `400 origin_required`, and an origin not on the allowlist fails with
81
+ `403 origin_not_allowed`.
82
+
83
+ ### Deprecated
84
+
85
+ - `source_scope` on `client.embed.create_session()` (sync and async): the
86
+ server does not enforce it, and it will be removed in v2.0.0. Passing it now
87
+ emits a `DeprecationWarning`; the field is still sent, so behavior is
88
+ unchanged. Scope sessions with access policies instead — see
89
+ `client.embed.get_session(access=...)`.
querri-2.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Querri, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,12 +1,13 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: querri
3
- Version: 1.0.8
3
+ Version: 2.0.0
4
4
  Summary: Python SDK and CLI for the Querri data analysis platform
5
5
  Project-URL: Homepage, https://querri.com
6
6
  Project-URL: Documentation, https://docs.querri.com/sdk/python
7
7
  Project-URL: Repository, https://github.com/Querri-inc/querri-python
8
8
  Author-email: Querri <support@querri.com>
9
9
  License-Expression: MIT
10
+ License-File: LICENSE
10
11
  Classifier: Development Status :: 3 - Alpha
11
12
  Classifier: Intended Audience :: Developers
12
13
  Classifier: License :: OSI Approved :: MIT License
@@ -117,7 +118,7 @@ See [`skills/querri-cli/SKILL.md`](skills/querri-cli/SKILL.md) for the full comm
117
118
 
118
119
  ## Python SDK
119
120
 
120
- For embedding Querri analytics in a Python web application. Use alongside the [`@querri-inc/embed`](https://www.npmjs.com/package/@querri-inc/embed) frontend component.
121
+ For embedding Querri analytics in a Python web application. The backend SDK creates embed sessions; the frontend loads the embed script directly from your Querri server at `{serverUrl}/sdk/querri-embed.js` (a React wrapper is also available — see below).
121
122
 
122
123
  ### Quick Start
123
124
 
@@ -164,15 +165,52 @@ def querri_session():
164
165
 
165
166
  Django and FastAPI follow the identical pattern — see **[docs/server-sdk.md](docs/server-sdk.md)** for those examples.
166
167
 
168
+ Always forward the request's `Origin` header as shown. If your organization configures an allowlist of embed domains, the server enforces it when the session is created: a missing origin fails with `400 origin_required`, and an origin not on the allowlist fails with `403 origin_not_allowed`. Without an allowlist, `origin` is accepted but not enforced.
169
+
170
+ ### Add the embed (browser)
171
+
172
+ Load the embed script from your Querri server — it is served at `{serverUrl}/sdk/querri-embed.js` and always matches the server's version:
173
+
174
+ ```html
175
+ <div id="querri" style="width: 100%; height: 600px;"></div>
176
+ <script src="https://app.querri.com/sdk/querri-embed.js"></script>
177
+ <script>
178
+ QuerriEmbed.create(document.getElementById('querri'), {
179
+ serverUrl: 'https://app.querri.com',
180
+ auth: {
181
+ fetchSessionToken: async () => {
182
+ const res = await fetch('/api/querri-session', { method: 'POST' });
183
+ const data = await res.json();
184
+ if (!res.ok) throw new Error(data.error || 'Session failed');
185
+ return data.session_token;
186
+ },
187
+ },
188
+ startView: '/home',
189
+ chrome: { rail: { show: true }, header: { show: true } },
190
+ });
191
+ </script>
192
+ ```
193
+
194
+ Use the same host for the script tag and `serverUrl`. See [`examples/web-embed/`](examples/web-embed/) for a working page that fetches the server URL from its backend before injecting the script.
195
+
167
196
  ### Add the embed (React)
168
197
 
198
+ A React wrapper is available (requires `@querri-inc/embed` >= 1.0.0):
199
+
169
200
  ```tsx
170
201
  import { QuerriEmbed } from '@querri-inc/embed/react';
171
202
 
172
203
  <QuerriEmbed
173
204
  style={{ width: '100%', height: '600px' }}
174
205
  serverUrl="https://app.querri.com"
175
- auth={{ sessionEndpoint: '/api/querri-session' }}
206
+ auth={{
207
+ fetchSessionToken: async () => {
208
+ const res = await fetch('/api/querri-session', { method: 'POST' });
209
+ const data = await res.json();
210
+ if (!res.ok) throw new Error(data.error || 'Session failed');
211
+ return data.session_token;
212
+ },
213
+ }}
176
214
  />
177
215
  ```
178
216
 
@@ -244,7 +282,7 @@ See **[docs/server-sdk.md](docs/server-sdk.md#user-scoped-client-as_user)** for
244
282
 
245
283
  | Resource | Access | Key Methods |
246
284
  |----------|--------|-------------|
247
- | `client.embed` | Embed sessions (flagship) | `get_session`, `create_session`, `refresh_session`, `revoke_session`, `list_sessions` |
285
+ | `client.embed` | Embed sessions (flagship) | `get_session`, `create_session`, `refresh_session`, `revoke_session`, `list_sessions`, `get_ui_config` |
248
286
  | `client.policies` | Row-level access control | `setup`, `create`, `list`, `get`, `update`, `delete`, `assign_users`, `remove_user`, `resolve`, `columns` |
249
287
  | `client.users` | User management | `list`, `create`, `get`, `get_or_create`, `update`, `delete` |
250
288
  | `client.dashboards` | Dashboard management | `list`, `create`, `get`, `update`, `delete`, `refresh`, `refresh_status` |
@@ -280,6 +318,7 @@ All errors extend `QuerriError`:
280
318
  QuerriError
281
319
  ├── APIError — HTTP error responses
282
320
  │ ├── ValidationError — 400
321
+ │ │ └── OriginRequiredError — 400 origin_required (org has an embed-domain allowlist)
283
322
  │ ├── AuthenticationError — 401
284
323
  │ ├── PermissionError — 403
285
324
  │ ├── NotFoundError — 404
@@ -315,3 +354,7 @@ pytest tests/test_integration.py -m integration -v # requires API credentials
315
354
  ## License
316
355
 
317
356
  MIT
357
+
358
+ ## Support policy
359
+
360
+ The 1.x line receives security fixes only, for 6 months from the 2.0.0 release (2026-08-31). Migrate with [docs/MIGRATION.md](docs/MIGRATION.md).
@@ -72,7 +72,7 @@ See [`skills/querri-cli/SKILL.md`](skills/querri-cli/SKILL.md) for the full comm
72
72
 
73
73
  ## Python SDK
74
74
 
75
- For embedding Querri analytics in a Python web application. Use alongside the [`@querri-inc/embed`](https://www.npmjs.com/package/@querri-inc/embed) frontend component.
75
+ For embedding Querri analytics in a Python web application. The backend SDK creates embed sessions; the frontend loads the embed script directly from your Querri server at `{serverUrl}/sdk/querri-embed.js` (a React wrapper is also available — see below).
76
76
 
77
77
  ### Quick Start
78
78
 
@@ -119,15 +119,52 @@ def querri_session():
119
119
 
120
120
  Django and FastAPI follow the identical pattern — see **[docs/server-sdk.md](docs/server-sdk.md)** for those examples.
121
121
 
122
+ Always forward the request's `Origin` header as shown. If your organization configures an allowlist of embed domains, the server enforces it when the session is created: a missing origin fails with `400 origin_required`, and an origin not on the allowlist fails with `403 origin_not_allowed`. Without an allowlist, `origin` is accepted but not enforced.
123
+
124
+ ### Add the embed (browser)
125
+
126
+ Load the embed script from your Querri server — it is served at `{serverUrl}/sdk/querri-embed.js` and always matches the server's version:
127
+
128
+ ```html
129
+ <div id="querri" style="width: 100%; height: 600px;"></div>
130
+ <script src="https://app.querri.com/sdk/querri-embed.js"></script>
131
+ <script>
132
+ QuerriEmbed.create(document.getElementById('querri'), {
133
+ serverUrl: 'https://app.querri.com',
134
+ auth: {
135
+ fetchSessionToken: async () => {
136
+ const res = await fetch('/api/querri-session', { method: 'POST' });
137
+ const data = await res.json();
138
+ if (!res.ok) throw new Error(data.error || 'Session failed');
139
+ return data.session_token;
140
+ },
141
+ },
142
+ startView: '/home',
143
+ chrome: { rail: { show: true }, header: { show: true } },
144
+ });
145
+ </script>
146
+ ```
147
+
148
+ Use the same host for the script tag and `serverUrl`. See [`examples/web-embed/`](examples/web-embed/) for a working page that fetches the server URL from its backend before injecting the script.
149
+
122
150
  ### Add the embed (React)
123
151
 
152
+ A React wrapper is available (requires `@querri-inc/embed` >= 1.0.0):
153
+
124
154
  ```tsx
125
155
  import { QuerriEmbed } from '@querri-inc/embed/react';
126
156
 
127
157
  <QuerriEmbed
128
158
  style={{ width: '100%', height: '600px' }}
129
159
  serverUrl="https://app.querri.com"
130
- auth={{ sessionEndpoint: '/api/querri-session' }}
160
+ auth={{
161
+ fetchSessionToken: async () => {
162
+ const res = await fetch('/api/querri-session', { method: 'POST' });
163
+ const data = await res.json();
164
+ if (!res.ok) throw new Error(data.error || 'Session failed');
165
+ return data.session_token;
166
+ },
167
+ }}
131
168
  />
132
169
  ```
133
170
 
@@ -199,7 +236,7 @@ See **[docs/server-sdk.md](docs/server-sdk.md#user-scoped-client-as_user)** for
199
236
 
200
237
  | Resource | Access | Key Methods |
201
238
  |----------|--------|-------------|
202
- | `client.embed` | Embed sessions (flagship) | `get_session`, `create_session`, `refresh_session`, `revoke_session`, `list_sessions` |
239
+ | `client.embed` | Embed sessions (flagship) | `get_session`, `create_session`, `refresh_session`, `revoke_session`, `list_sessions`, `get_ui_config` |
203
240
  | `client.policies` | Row-level access control | `setup`, `create`, `list`, `get`, `update`, `delete`, `assign_users`, `remove_user`, `resolve`, `columns` |
204
241
  | `client.users` | User management | `list`, `create`, `get`, `get_or_create`, `update`, `delete` |
205
242
  | `client.dashboards` | Dashboard management | `list`, `create`, `get`, `update`, `delete`, `refresh`, `refresh_status` |
@@ -235,6 +272,7 @@ All errors extend `QuerriError`:
235
272
  QuerriError
236
273
  ├── APIError — HTTP error responses
237
274
  │ ├── ValidationError — 400
275
+ │ │ └── OriginRequiredError — 400 origin_required (org has an embed-domain allowlist)
238
276
  │ ├── AuthenticationError — 401
239
277
  │ ├── PermissionError — 403
240
278
  │ ├── NotFoundError — 404
@@ -270,3 +308,7 @@ pytest tests/test_integration.py -m integration -v # requires API credentials
270
308
  ## License
271
309
 
272
310
  MIT
311
+
312
+ ## Support policy
313
+
314
+ The 1.x line receives security fixes only, for 6 months from the 2.0.0 release (2026-08-31). Migrate with [docs/MIGRATION.md](docs/MIGRATION.md).
@@ -0,0 +1,120 @@
1
+ # Migrating from 1.x to 2.0
2
+
3
+ v2.0.0 removes a fake security parameter, aligns the user-scoped client with
4
+ what the server actually authorizes, and makes two silent server behaviors
5
+ loud. Most 1.x code runs unchanged; the breaking changes are below, each with
6
+ before/after.
7
+
8
+ ## `source_scope` removed from `create_session()`
9
+
10
+ **This is a security fix, not a cleanup.** `source_scope` was accepted by the
11
+ SDK and sent to the server, but the server never enforced it. Code that passed
12
+ it got sessions that *looked* scoped to specific sources and were not — a
13
+ silently-ignored access-control parameter is worse than none, because it reads
14
+ as a guarantee. v1.1.1 started warning; v2.0.0 removes it, so passing it now
15
+ raises `TypeError`.
16
+
17
+ Scope sessions with access policies, which the server enforces:
18
+
19
+ ```python
20
+ # Before (1.x) — source_scope was silently ignored
21
+ session = client.embed.create_session(
22
+ user_id="usr_1",
23
+ source_scope=["src_sales"], # did nothing
24
+ )
25
+
26
+ # After (2.0) — access policies are enforced server-side
27
+ session = client.embed.get_session(
28
+ user="customer-42",
29
+ access={"sources": ["src_sales"], "filters": {"tenant_id": "acme"}},
30
+ )
31
+ ```
32
+
33
+ `get_session()` resolves the user, finds-or-creates a policy from the access
34
+ spec, assigns it, and mints the session in one call. If you manage policies
35
+ yourself, use `client.policies.setup(...)` / `assign_users(...)` and then
36
+ `create_session(user_id=...)` — the session inherits whatever the user's
37
+ policies grant.
38
+
39
+ ## `as_user()` now calls `/api/v1`
40
+
41
+ `UserQuerri` previously called the internal API at `{host}/api`. Embed
42
+ sessions are the public API's highest-priority credential, so user-scoped
43
+ clients now call the same `/api/v1` paths as the admin client, still with the
44
+ `X-Embed-Session` header. No code change needed unless you depended on the
45
+ URL (e.g. in proxies, allowlists, or request logging):
46
+
47
+ ```python
48
+ # Same code in 1.x and 2.0 — only the wire paths changed:
49
+ # 1.x: GET {host}/api/projects
50
+ # 2.0: GET {host}/api/v1/projects
51
+ with client.as_user(session) as uc:
52
+ for project in uc.projects.list():
53
+ print(project.name)
54
+ ```
55
+
56
+ ## User-scoped dashboards are read-only
57
+
58
+ Embed sessions exclude the `admin:dashboards:write` scope server-side —
59
+ writes always failed with 403. The SDK surface now says so:
60
+ `user_client.dashboards` exposes `list()`, `get()`, and `refresh_status()`
61
+ only.
62
+
63
+ ```python
64
+ # Before (1.x) — compiled, then 403'd at runtime
65
+ with client.as_user(session) as uc:
66
+ uc.dashboards.delete("dash_1") # AttributeError in 2.0
67
+
68
+ # After (2.0) — dashboard writes belong to the admin client
69
+ client.dashboards.delete("dash_1")
70
+ ```
71
+
72
+ Similarly, there is no `user_client.embed` (there never was one that worked:
73
+ `embed:session:create` is excluded from embed-session scopes). Mint and
74
+ manage sessions from the admin client.
75
+
76
+ ## `ttl` outside [900, 86400] raises `ValueError`
77
+
78
+ The server silently clamps out-of-range TTLs. The SDK now refuses to send a
79
+ TTL that would not be honored as given:
80
+
81
+ ```python
82
+ # Before (1.x) — sent ttl=60, server silently created a 900s session
83
+ client.embed.create_session(user_id="usr_1", ttl=60)
84
+
85
+ # After (2.0) — fails loudly before any HTTP
86
+ # ValueError: ttl must be between 900 and 86400 seconds, got 60
87
+ ```
88
+
89
+ ## `400 origin_required` raises `OriginRequiredError`
90
+
91
+ When the org configures an embed-domain allowlist and no `origin` is passed,
92
+ the SDK now raises `OriginRequiredError` — a subclass of `ValidationError`,
93
+ so existing `except ValidationError` handlers keep working — with a message
94
+ that says what to do (pass the embedding page's origin):
95
+
96
+ ```python
97
+ from querri import OriginRequiredError
98
+
99
+ try:
100
+ session = client.embed.create_session(user_id="usr_1")
101
+ except OriginRequiredError:
102
+ session = client.embed.create_session(
103
+ user_id="usr_1", origin="https://app.customer.com"
104
+ )
105
+ ```
106
+
107
+ ## `revoke_user_sessions()` paginates
108
+
109
+ In 1.x it revoked at most whatever one listing returned (the server caps
110
+ listings at 200 with no cursor). It now loops revoke-and-relist until a
111
+ listing shows no sessions for the user. The signature and return value are
112
+ unchanged; only the completeness improved — expect more requests (and a
113
+ larger returned count) for users with many sessions.
114
+
115
+ ## New in 2.0 (non-breaking)
116
+
117
+ - `client.embed.get_ui_config(org)` — the public embed UI config
118
+ (`{chrome, theme, privacy}`) from `GET {host}/api/embed/ui-config?org=...`.
119
+ - `querri session ui-config --org <id>` — CLI for the same.
120
+ - `querri share source org SOURCE_ID --disable` — turn org-wide sharing off.