authpi-admin 0.3.0__tar.gz → 0.8.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 (64) hide show
  1. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/.gitignore +9 -0
  2. authpi_admin-0.8.0/CHANGELOG.md +78 -0
  3. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/PKG-INFO +69 -12
  4. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/README.md +68 -11
  5. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/__init__.py +4 -2
  6. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/_version.py +1 -1
  7. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/client.py +33 -16
  8. authpi_admin-0.8.0/authpi_admin/generated/__init__.py +38 -0
  9. authpi_admin-0.8.0/authpi_admin/generated/models.py +801 -0
  10. authpi_admin-0.3.0/authpi_admin/generated/resources/api_keys.py → authpi_admin-0.8.0/authpi_admin/generated/resources/account_api_keys.py +94 -13
  11. authpi_admin-0.8.0/authpi_admin/generated/resources/account_tokens.py +86 -0
  12. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/accounts.py +12 -55
  13. authpi_admin-0.3.0/authpi_admin/generated/resources/verifiers.py → authpi_admin-0.8.0/authpi_admin/generated/resources/agent_verifiers.py +9 -7
  14. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/agents.py +14 -9
  15. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/approvals.py +6 -4
  16. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/auth_methods.py +14 -9
  17. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/clients.py +9 -6
  18. authpi_admin-0.8.0/authpi_admin/generated/resources/credits.py +102 -0
  19. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/deliveries.py +3 -2
  20. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/domains.py +16 -10
  21. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/events.py +57 -4
  22. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/groups.py +17 -15
  23. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/invitations.py +19 -15
  24. authpi_admin-0.3.0/authpi_admin/generated/resources/sessions.py → authpi_admin-0.8.0/authpi_admin/generated/resources/issuer_sessions.py +11 -10
  25. authpi_admin-0.8.0/authpi_admin/generated/resources/issuer_tokens.py +46 -0
  26. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/issuers.py +14 -9
  27. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/members.py +15 -12
  28. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/notes.py +68 -9
  29. authpi_admin-0.3.0/authpi_admin/generated/resources/trusted_devices.py → authpi_admin-0.8.0/authpi_admin/generated/resources/organization_api_keys.py +25 -31
  30. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/organizations.py +13 -73
  31. authpi_admin-0.8.0/authpi_admin/generated/resources/payment_methods.py +95 -0
  32. authpi_admin-0.8.0/authpi_admin/generated/resources/sso.py +135 -0
  33. authpi_admin-0.8.0/authpi_admin/generated/resources/trusted_devices.py +135 -0
  34. authpi_admin-0.8.0/authpi_admin/generated/resources/user_sessions.py +63 -0
  35. authpi_admin-0.3.0/authpi_admin/generated/resources/tokens.py → authpi_admin-0.8.0/authpi_admin/generated/resources/user_tokens.py +9 -7
  36. authpi_admin-0.3.0/authpi_admin/generated/resources/users.py → authpi_admin-0.8.0/authpi_admin/generated/resources/user_verifiers.py +86 -64
  37. authpi_admin-0.8.0/authpi_admin/generated/resources/users.py +164 -0
  38. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/webhooks.py +11 -9
  39. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/scopes/agent_scope.py +8 -9
  40. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/scopes/issuer_scope.py +19 -39
  41. authpi_admin-0.8.0/authpi_admin/generated/scopes/organization_scope.py +107 -0
  42. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/scopes/user_scope.py +15 -16
  43. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/scopes/webhook_scope.py +10 -8
  44. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/http_client.py +98 -17
  45. authpi_admin-0.8.0/authpi_admin/types.py +7 -0
  46. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/pyproject.toml +1 -1
  47. authpi_admin-0.8.0/tests/test_client.py +176 -0
  48. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/tests/test_http_client.py +60 -18
  49. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/tests/test_integration.py +55 -31
  50. authpi_admin-0.8.0/tests/test_public_types.py +22 -0
  51. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/uv.lock +240 -238
  52. authpi_admin-0.3.0/CHANGELOG.md +0 -25
  53. authpi_admin-0.3.0/authpi_admin/generated/__init__.py +0 -28
  54. authpi_admin-0.3.0/authpi_admin/generated/models.py +0 -380
  55. authpi_admin-0.3.0/tests/test_client.py +0 -90
  56. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/errors.py +0 -0
  57. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/resources/__init__.py +0 -0
  58. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/generated/scopes/__init__.py +0 -0
  59. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/pagination.py +0 -0
  60. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/py.typed +0 -0
  61. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/authpi_admin/user_agent.py +0 -0
  62. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/tests/__init__.py +0 -0
  63. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/tests/test_errors.py +0 -0
  64. {authpi_admin-0.3.0 → authpi_admin-0.8.0}/tests/test_pagination.py +0 -0
@@ -13,6 +13,7 @@ vite.config.js.timestamp-*
13
13
  vite.config.ts.timestamp-*
14
14
  oracle.sql
15
15
  dist
16
+ dist-templates/
16
17
  storybook-static
17
18
  target
18
19
  filter/
@@ -29,6 +30,7 @@ test-adyen/
29
30
  stripe-authpi/
30
31
  stripe-app/
31
32
  secrets.json
33
+ *.secrets.json
32
34
  .idea
33
35
  .vscode
34
36
  *.pem
@@ -49,3 +51,10 @@ worker-configuration.d.ts
49
51
  .worktrees/
50
52
  docs/plans/
51
53
  docs/superpowers/
54
+ docs/.obsidian/
55
+
56
+ # Operator-rendered ops-db bootstrap files — carry environment-specific
57
+ # captured `agt_*` IDs from the Step 3 bootstrap. The .template stays in
58
+ # git as the canonical structure; rendered copies stay local.
59
+ d1-databases/ops-db/0005_bootstrap_capability_matrix.sql
60
+ d1-databases/ops-db/0005_bootstrap_capability_matrix_prod.sql
@@ -0,0 +1,78 @@
1
+ # Changelog
2
+
3
+ ## [0.8.0](https://github.com/arbfay/authpi/compare/authpi-admin-v0.7.0...authpi-admin-v0.8.0) (2026-06-21)
4
+
5
+
6
+ ### Features
7
+
8
+ * **admin-py:** release public SDK types module ([20a8329](https://github.com/arbfay/authpi/commit/20a8329e8ddda0923a8dfd7e8d2380c060ea3f42))
9
+
10
+ ## [0.7.0](https://github.com/arbfay/authpi/compare/authpi-admin-v0.6.0...authpi-admin-v0.7.0) (2026-06-12)
11
+
12
+
13
+ ### Features
14
+
15
+ * GET /v1/me — caller identity endpoint (AUT-163) ([#286](https://github.com/arbfay/authpi/issues/286)) ([b9f35dc](https://github.com/arbfay/authpi/commit/b9f35dc2fd8cca77cdd2079b240ce28dc11d35a8))
16
+
17
+ ## [0.6.0](https://github.com/arbfay/authpi/compare/authpi-admin-v0.4.0...authpi-admin-v0.6.0) (2026-06-11)
18
+
19
+
20
+ ### ⚠ BREAKING CHANGES
21
+
22
+ * **admin-py:** collision-split class renames (see admin-ts) and issuer-scope org-subresource accessors moved to the organization scope; the removed accessors 404'd on every call, so no working integration is affected.
23
+ * **admin-ts:** cross-scope name collisions split generated classes (ApiKeysResource -> AccountApiKeysResource, TokensResource -> AccountTokensResource, SessionsResource -> IssuerSessionsResource / UserSessionsResource, VerifiersResource -> AgentVerifiersResource / UserVerifiersResource), and the issuer-scope groups/invitations/ members/domains accessors moved to the new organization scope. The removed accessors produced 404s on every call, so no working integration is affected.
24
+
25
+ ### Features
26
+
27
+ * **admin-py:** regenerate from corrected spec — org scope, restored endpoints, credits ([2cd96b5](https://github.com/arbfay/authpi/commit/2cd96b569ab9ddff8589fd8e786a16052af4c7e5))
28
+ * **admin-ts:** regenerate from corrected spec — org scope, restored endpoints, credits ([3932b60](https://github.com/arbfay/authpi/commit/3932b609116f059dff36a73f9a5378c21d69eb3d))
29
+
30
+
31
+ ### Bug Fixes
32
+
33
+ * **generators:** action methods carry query params and real response types ([0f515ec](https://github.com/arbfay/authpi/commit/0f515ecc1ce737f62e2826ff3106b97c35a6ce2f))
34
+
35
+ ## [0.4.0](https://github.com/arbfay/authpi/compare/authpi-admin-v0.3.1...authpi-admin-v0.4.0) (2026-06-11)
36
+
37
+
38
+ ### ⚠ BREAKING CHANGES
39
+
40
+ * **admin-py:** `account_id` is now required for both auth modes. The api_key-without-account_id form never produced a successful request.
41
+ * **admin-py:** `api_key` changed from `str` to `tuple[str, str]`. The previous form never authenticated successfully, so no working integration is affected.
42
+
43
+ ### Bug Fixes
44
+
45
+ * **admin-py:** default base_url to the production API ([4351942](https://github.com/arbfay/authpi/commit/4351942f3c40d75b24e079e1d83553e6f58a3303))
46
+ * **admin-py:** require account_id — the API has no accounts/me alias ([58cf3a2](https://github.com/arbfay/authpi/commit/58cf3a2a1af402f7b9e20ceadd84e6015957f000))
47
+ * **admin-py:** send API keys as HTTP Basic id:secret pair ([aee64f7](https://github.com/arbfay/authpi/commit/aee64f777abb18ecb4578c178cf55a4c72abb046))
48
+
49
+ ## [0.3.1](https://github.com/arbfay/authpi/compare/authpi-admin-v0.3.0...authpi-admin-v0.3.1) (2026-06-10)
50
+
51
+
52
+ ### Bug Fixes
53
+
54
+ * **admin-py:** unwrap the single-resource data envelope in generated methods ([d63ee44](https://github.com/arbfay/authpi/commit/d63ee44d9db64e46dbfbfc5da66c4a1aaeacf609))
55
+
56
+ ## [0.3.0](https://github.com/arbfay/authpi/compare/authpi-admin-v0.2.0...authpi-admin-v0.3.0) (2026-04-17)
57
+
58
+
59
+ ### Features
60
+
61
+ * **sdks:** set custom User-Agent for SDK identification ([#213](https://github.com/arbfay/authpi/issues/213)) ([dec716c](https://github.com/arbfay/authpi/commit/dec716c99d877e99496f45c69f3625341bb2bc2e))
62
+
63
+ ## [0.2.0](https://github.com/arbfay/authpi/compare/authpi-admin-v0.1.0...authpi-admin-v0.2.0) (2026-04-13)
64
+
65
+
66
+ ### ⚠ BREAKING CHANGES
67
+
68
+ * All generated type interfaces now use snake_case field names. Consumers referencing camelCase fields (e.g., Client.issuerId) must update to snake_case (Client.issuer_id).
69
+
70
+ ### Features
71
+
72
+ * Admin SDK code generator + Python SDK (authpi-admin) ([#186](https://github.com/arbfay/authpi/issues/186)) ([3eb5055](https://github.com/arbfay/authpi/commit/3eb505579f34a49769e0b01eb59f6d571ad74571))
73
+ * SDK convergence — extend @authpi/admin, migrate console, security fixes ([#197](https://github.com/arbfay/authpi/issues/197)) ([3d4896f](https://github.com/arbfay/authpi/commit/3d4896fa60381542d99a66e394809d128bf6bcf0))
74
+
75
+
76
+ ### Bug Fixes
77
+
78
+ * **admin:** fix custom action paths, security hardening for TS and Python SDKs ([#189](https://github.com/arbfay/authpi/issues/189)) ([bf5e0b4](https://github.com/arbfay/authpi/commit/bf5e0b430a76173a1a1b4ff188095a96b337a74c))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: authpi-admin
3
- Version: 0.3.0
3
+ Version: 0.8.0
4
4
  Summary: Official Python Admin SDK for AuthPI Core API
5
5
  Project-URL: Homepage, https://authpi.com
6
6
  Project-URL: Documentation, https://docs.authpi.com/sdk/python/admin
@@ -46,8 +46,9 @@ pip install authpi-admin
46
46
 
47
47
  ```python
48
48
  from authpi_admin import AuthPIAdmin
49
+ from authpi_admin.types import WebhookEventType
49
50
 
50
- async with AuthPIAdmin(api_key="key_xxx") as admin:
51
+ async with AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx") as admin:
51
52
  # List issuers
52
53
  page = await admin.issuers.list(limit=10)
53
54
  print(page.data)
@@ -64,14 +65,41 @@ async with AuthPIAdmin(api_key="key_xxx") as admin:
64
65
  "email": "alice@example.com",
65
66
  "display_name": "Alice",
66
67
  })
68
+
69
+ # Create a webhook with typed event subscriptions
70
+ webhook = await admin.webhooks.create({
71
+ "name": "Lifecycle events",
72
+ "url": "https://example.com/webhooks/authpi",
73
+ "auth": {"type": "signature"},
74
+ "events": [
75
+ WebhookEventType.ORGANIZATION_CREATED,
76
+ WebhookEventType.USER_CREATED,
77
+ ],
78
+ })
79
+ ```
80
+
81
+ Generated response models, request `TypedDict`s, and enums are exported from `authpi_admin.types`. Most calls can use dict literals directly; import request types only when you want to annotate a reusable payload:
82
+
83
+ ```python
84
+ from authpi_admin.types import CreateWebhookInput, WebhookEventType
85
+
86
+ payload: CreateWebhookInput = {
87
+ "name": "Lifecycle events",
88
+ "url": "https://example.com/webhooks/authpi",
89
+ "auth": {"type": "signature"},
90
+ "events": [WebhookEventType.USER_CREATED],
91
+ }
92
+ await admin.webhooks.create(payload)
67
93
  ```
68
94
 
69
95
  ## Authentication
70
96
 
71
97
  ### API Key (default)
72
98
 
99
+ API keys are issued as an **id + secret pair** — both parts are shown once when you create the key in the dashboard. The SDK sends them as HTTP Basic credentials (`key_id:key_secret`):
100
+
73
101
  ```python
74
- admin = AuthPIAdmin(api_key="key_xxx")
102
+ admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx")
75
103
  ```
76
104
 
77
105
  ### Bearer Token
@@ -101,6 +129,24 @@ admin = AuthPIAdmin(
101
129
 
102
130
  When `on_token_expired` is provided, the SDK calls it on 401 responses and retries the request with the new token. Concurrent 401s are deduplicated — only one refresh runs at a time.
103
131
 
132
+ ### Account resolution
133
+
134
+ `account_id` is optional. When omitted, the SDK resolves it once via `GET /v1/me` on the first request and caches it — an API key always maps to exactly one account:
135
+
136
+ ```python
137
+ async with AuthPIAdmin(api_key=("key_xxx", "your_key_secret")) as admin:
138
+ issuers = await admin.issuers.list() # resolves the account transparently
139
+ ```
140
+
141
+ If the credential can act on zero or multiple accounts (possible with user bearer tokens), the SDK raises a `ConfigurationError` naming the choices — pass `account_id` explicitly in that case.
142
+
143
+ You can also ask directly who the API considers you to be:
144
+
145
+ ```python
146
+ me = await admin.whoami()
147
+ # {"type": "api_key", "key_id": "key_...", "issuer_id": "i_...", "accounts": [{"account_id": ..., "org_id": ..., "scopes": [...]}]}
148
+ ```
149
+
104
150
  ## Scoped Client Pattern
105
151
 
106
152
  The SDK mirrors the API's resource hierarchy. Navigate with chained accessors:
@@ -108,7 +154,12 @@ The SDK mirrors the API's resource hierarchy. Navigate with chained accessors:
108
154
  ```python
109
155
  # Account-level resources
110
156
  await admin.issuers.list()
111
- await admin.webhooks.create({"url": "https://..."})
157
+ await admin.webhooks.create({
158
+ "name": "Lifecycle events",
159
+ "url": "https://...",
160
+ "auth": {"type": "signature"},
161
+ "events": [WebhookEventType.USER_CREATED],
162
+ })
112
163
  await admin.events.list(limit=50)
113
164
 
114
165
  # Issuer scope
@@ -119,6 +170,10 @@ await iss.clients.list()
119
170
  await iss.organizations.list()
120
171
 
121
172
  # User scope (nested under issuer)
173
+ org = admin.issuer("iss_xxx").organization("org_xxx")
174
+ await org.members.list()
175
+ await org.sso.add_domain(domain="acme.com")
176
+
122
177
  usr = admin.issuer("iss_xxx").user("usr_xxx")
123
178
  await usr.get()
124
179
  await usr.sessions.list()
@@ -160,14 +215,15 @@ Read-only requests (GET, HEAD, OPTIONS) are automatically retried on 429, 502, 5
160
215
 
161
216
  ```python
162
217
  # Default: retries enabled (3 attempts, 1s base delay, exponential backoff)
163
- admin = AuthPIAdmin(api_key="key_xxx")
218
+ admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx")
164
219
 
165
220
  # Disable retries
166
- admin = AuthPIAdmin(api_key="key_xxx", retries=False)
221
+ admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), retries=False)
167
222
 
168
223
  # Custom retry config
169
224
  admin = AuthPIAdmin(
170
- api_key="key_xxx",
225
+ api_key=("key_xxx", "your_key_secret"),
226
+ account_id="acc_xxx",
171
227
  retries={"limit": 5, "delay": 0.5, "backoff": "linear"},
172
228
  )
173
229
  ```
@@ -239,8 +295,9 @@ except AuthenticationError:
239
295
  from authpi_admin import AuthPIAdmin
240
296
 
241
297
  admin = AuthPIAdmin(
242
- api_key="key_xxx", # or access_token + account_id
243
- base_url="https://api.authpi.dev", # default
298
+ api_key=("key_xxx", "your_key_secret"), # or access_token instead
299
+ account_id="acc_xxx", # optional — resolved via GET /v1/me when omitted
300
+ base_url="https://api.authpi.com", # default
244
301
  timeout=30.0, # default, in seconds
245
302
  default_headers={"X-Custom": "value"}, # optional extra headers
246
303
  retries=True, # default (or False, or dict)
@@ -256,7 +313,7 @@ import httpx
256
313
  from authpi_admin import AuthPIAdmin
257
314
 
258
315
  async with httpx.AsyncClient(proxies="http://proxy:8080") as http:
259
- admin = AuthPIAdmin(api_key="key_xxx", http_client=http)
316
+ admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), http_client=http)
260
317
  await admin.issuers.list()
261
318
  ```
262
319
 
@@ -265,7 +322,7 @@ async with httpx.AsyncClient(proxies="http://proxy:8080") as http:
265
322
  The SDK supports async context managers for clean resource cleanup:
266
323
 
267
324
  ```python
268
- async with AuthPIAdmin(api_key="key_xxx") as admin:
325
+ async with AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx") as admin:
269
326
  await admin.issuers.list()
270
327
  # httpx client is closed automatically
271
328
  ```
@@ -273,7 +330,7 @@ async with AuthPIAdmin(api_key="key_xxx") as admin:
273
330
  Or close manually:
274
331
 
275
332
  ```python
276
- admin = AuthPIAdmin(api_key="key_xxx")
333
+ admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx")
277
334
  try:
278
335
  await admin.issuers.list()
279
336
  finally:
@@ -14,8 +14,9 @@ pip install authpi-admin
14
14
 
15
15
  ```python
16
16
  from authpi_admin import AuthPIAdmin
17
+ from authpi_admin.types import WebhookEventType
17
18
 
18
- async with AuthPIAdmin(api_key="key_xxx") as admin:
19
+ async with AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx") as admin:
19
20
  # List issuers
20
21
  page = await admin.issuers.list(limit=10)
21
22
  print(page.data)
@@ -32,14 +33,41 @@ async with AuthPIAdmin(api_key="key_xxx") as admin:
32
33
  "email": "alice@example.com",
33
34
  "display_name": "Alice",
34
35
  })
36
+
37
+ # Create a webhook with typed event subscriptions
38
+ webhook = await admin.webhooks.create({
39
+ "name": "Lifecycle events",
40
+ "url": "https://example.com/webhooks/authpi",
41
+ "auth": {"type": "signature"},
42
+ "events": [
43
+ WebhookEventType.ORGANIZATION_CREATED,
44
+ WebhookEventType.USER_CREATED,
45
+ ],
46
+ })
47
+ ```
48
+
49
+ Generated response models, request `TypedDict`s, and enums are exported from `authpi_admin.types`. Most calls can use dict literals directly; import request types only when you want to annotate a reusable payload:
50
+
51
+ ```python
52
+ from authpi_admin.types import CreateWebhookInput, WebhookEventType
53
+
54
+ payload: CreateWebhookInput = {
55
+ "name": "Lifecycle events",
56
+ "url": "https://example.com/webhooks/authpi",
57
+ "auth": {"type": "signature"},
58
+ "events": [WebhookEventType.USER_CREATED],
59
+ }
60
+ await admin.webhooks.create(payload)
35
61
  ```
36
62
 
37
63
  ## Authentication
38
64
 
39
65
  ### API Key (default)
40
66
 
67
+ API keys are issued as an **id + secret pair** — both parts are shown once when you create the key in the dashboard. The SDK sends them as HTTP Basic credentials (`key_id:key_secret`):
68
+
41
69
  ```python
42
- admin = AuthPIAdmin(api_key="key_xxx")
70
+ admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx")
43
71
  ```
44
72
 
45
73
  ### Bearer Token
@@ -69,6 +97,24 @@ admin = AuthPIAdmin(
69
97
 
70
98
  When `on_token_expired` is provided, the SDK calls it on 401 responses and retries the request with the new token. Concurrent 401s are deduplicated — only one refresh runs at a time.
71
99
 
100
+ ### Account resolution
101
+
102
+ `account_id` is optional. When omitted, the SDK resolves it once via `GET /v1/me` on the first request and caches it — an API key always maps to exactly one account:
103
+
104
+ ```python
105
+ async with AuthPIAdmin(api_key=("key_xxx", "your_key_secret")) as admin:
106
+ issuers = await admin.issuers.list() # resolves the account transparently
107
+ ```
108
+
109
+ If the credential can act on zero or multiple accounts (possible with user bearer tokens), the SDK raises a `ConfigurationError` naming the choices — pass `account_id` explicitly in that case.
110
+
111
+ You can also ask directly who the API considers you to be:
112
+
113
+ ```python
114
+ me = await admin.whoami()
115
+ # {"type": "api_key", "key_id": "key_...", "issuer_id": "i_...", "accounts": [{"account_id": ..., "org_id": ..., "scopes": [...]}]}
116
+ ```
117
+
72
118
  ## Scoped Client Pattern
73
119
 
74
120
  The SDK mirrors the API's resource hierarchy. Navigate with chained accessors:
@@ -76,7 +122,12 @@ The SDK mirrors the API's resource hierarchy. Navigate with chained accessors:
76
122
  ```python
77
123
  # Account-level resources
78
124
  await admin.issuers.list()
79
- await admin.webhooks.create({"url": "https://..."})
125
+ await admin.webhooks.create({
126
+ "name": "Lifecycle events",
127
+ "url": "https://...",
128
+ "auth": {"type": "signature"},
129
+ "events": [WebhookEventType.USER_CREATED],
130
+ })
80
131
  await admin.events.list(limit=50)
81
132
 
82
133
  # Issuer scope
@@ -87,6 +138,10 @@ await iss.clients.list()
87
138
  await iss.organizations.list()
88
139
 
89
140
  # User scope (nested under issuer)
141
+ org = admin.issuer("iss_xxx").organization("org_xxx")
142
+ await org.members.list()
143
+ await org.sso.add_domain(domain="acme.com")
144
+
90
145
  usr = admin.issuer("iss_xxx").user("usr_xxx")
91
146
  await usr.get()
92
147
  await usr.sessions.list()
@@ -128,14 +183,15 @@ Read-only requests (GET, HEAD, OPTIONS) are automatically retried on 429, 502, 5
128
183
 
129
184
  ```python
130
185
  # Default: retries enabled (3 attempts, 1s base delay, exponential backoff)
131
- admin = AuthPIAdmin(api_key="key_xxx")
186
+ admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx")
132
187
 
133
188
  # Disable retries
134
- admin = AuthPIAdmin(api_key="key_xxx", retries=False)
189
+ admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), retries=False)
135
190
 
136
191
  # Custom retry config
137
192
  admin = AuthPIAdmin(
138
- api_key="key_xxx",
193
+ api_key=("key_xxx", "your_key_secret"),
194
+ account_id="acc_xxx",
139
195
  retries={"limit": 5, "delay": 0.5, "backoff": "linear"},
140
196
  )
141
197
  ```
@@ -207,8 +263,9 @@ except AuthenticationError:
207
263
  from authpi_admin import AuthPIAdmin
208
264
 
209
265
  admin = AuthPIAdmin(
210
- api_key="key_xxx", # or access_token + account_id
211
- base_url="https://api.authpi.dev", # default
266
+ api_key=("key_xxx", "your_key_secret"), # or access_token instead
267
+ account_id="acc_xxx", # optional — resolved via GET /v1/me when omitted
268
+ base_url="https://api.authpi.com", # default
212
269
  timeout=30.0, # default, in seconds
213
270
  default_headers={"X-Custom": "value"}, # optional extra headers
214
271
  retries=True, # default (or False, or dict)
@@ -224,7 +281,7 @@ import httpx
224
281
  from authpi_admin import AuthPIAdmin
225
282
 
226
283
  async with httpx.AsyncClient(proxies="http://proxy:8080") as http:
227
- admin = AuthPIAdmin(api_key="key_xxx", http_client=http)
284
+ admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), http_client=http)
228
285
  await admin.issuers.list()
229
286
  ```
230
287
 
@@ -233,7 +290,7 @@ async with httpx.AsyncClient(proxies="http://proxy:8080") as http:
233
290
  The SDK supports async context managers for clean resource cleanup:
234
291
 
235
292
  ```python
236
- async with AuthPIAdmin(api_key="key_xxx") as admin:
293
+ async with AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx") as admin:
237
294
  await admin.issuers.list()
238
295
  # httpx client is closed automatically
239
296
  ```
@@ -241,7 +298,7 @@ async with AuthPIAdmin(api_key="key_xxx") as admin:
241
298
  Or close manually:
242
299
 
243
300
  ```python
244
- admin = AuthPIAdmin(api_key="key_xxx")
301
+ admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx")
245
302
  try:
246
303
  await admin.issuers.list()
247
304
  finally:
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.3.0"
5
+ __version__ = "0.8.0"
6
6
 
7
7
  from authpi_admin.client import AuthPIAdmin
8
8
  from authpi_admin.errors import (
@@ -23,11 +23,12 @@ from authpi_admin.errors import (
23
23
  UnexpectedError,
24
24
  ValidationError,
25
25
  )
26
+ from authpi_admin.generated.scopes.agent_scope import AgentScope
26
27
  from authpi_admin.generated.scopes.issuer_scope import IssuerScope
27
28
  from authpi_admin.generated.scopes.user_scope import UserScope
28
- from authpi_admin.generated.scopes.agent_scope import AgentScope
29
29
  from authpi_admin.generated.scopes.webhook_scope import WebhookScope
30
30
  from authpi_admin.pagination import Page
31
+ from authpi_admin.types import WebhookEventType
31
32
 
32
33
  __all__ = [
33
34
  "AgentScope",
@@ -51,5 +52,6 @@ __all__ = [
51
52
  "UnexpectedError",
52
53
  "UserScope",
53
54
  "ValidationError",
55
+ "WebhookEventType",
54
56
  "WebhookScope",
55
57
  ]
@@ -1,5 +1,5 @@
1
1
  """SDK version — kept in sync with pyproject.toml via release-please."""
2
2
 
3
3
  # x-release-please-start-version
4
- SDK_VERSION = "0.3.0"
4
+ SDK_VERSION = "0.8.0"
5
5
  # x-release-please-end
@@ -2,23 +2,25 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Awaitable, Callable
5
6
  from functools import cached_property
6
- from typing import TYPE_CHECKING, Any, Awaitable, Callable
7
+ from typing import TYPE_CHECKING, Any
7
8
 
8
9
  import httpx
9
10
 
10
11
  from authpi_admin.errors import ConfigurationError
11
- from authpi_admin.http_client import HttpClient
12
+ from authpi_admin.generated.resources.account_api_keys import AccountApiKeysResource
13
+ from authpi_admin.generated.resources.account_tokens import AccountTokensResource
12
14
  from authpi_admin.generated.resources.accounts import AccountsResource
13
- from authpi_admin.generated.resources.api_keys import ApiKeysResource
15
+ from authpi_admin.generated.resources.credits import CreditsResource
14
16
  from authpi_admin.generated.resources.domains import DomainsResource
15
17
  from authpi_admin.generated.resources.events import EventsResource
16
18
  from authpi_admin.generated.resources.issuers import IssuersResource
17
19
  from authpi_admin.generated.resources.notes import NotesResource
18
- from authpi_admin.generated.resources.tokens import TokensResource
19
20
  from authpi_admin.generated.resources.webhooks import WebhooksResource
20
21
  from authpi_admin.generated.scopes.issuer_scope import IssuerScope
21
22
  from authpi_admin.generated.scopes.webhook_scope import WebhookScope
23
+ from authpi_admin.http_client import UNRESOLVED_ACCOUNT_ID, HttpClient
22
24
 
23
25
  if TYPE_CHECKING:
24
26
  from types import TracebackType
@@ -27,8 +29,11 @@ if TYPE_CHECKING:
27
29
  class AuthPIAdmin:
28
30
  """Entry-point client for the AuthPI Core API.
29
31
 
30
- Usage with API key:
31
- async with AuthPIAdmin(api_key="key_xxx") as admin:
32
+ Usage with API key (id + secret pair, sent as HTTP Basic):
33
+ async with AuthPIAdmin(
34
+ api_key=("key_xxx", "your_key_secret"),
35
+ account_id="acc_xxx",
36
+ ) as admin:
32
37
  users = await admin.issuer("iss_xxx").users.list()
33
38
 
34
39
  Usage with bearer token:
@@ -42,11 +47,11 @@ class AuthPIAdmin:
42
47
  def __init__(
43
48
  self,
44
49
  *,
45
- api_key: str | None = None,
50
+ api_key: tuple[str, str] | None = None,
46
51
  access_token: str | None = None,
47
52
  on_token_expired: Callable[[], Awaitable[dict[str, str]]] | None = None,
48
53
  account_id: str | None = None,
49
- base_url: str = "https://api.authpi.dev",
54
+ base_url: str = "https://api.authpi.com",
50
55
  http_client: httpx.AsyncClient | None = None,
51
56
  timeout: float = 30.0,
52
57
  default_headers: dict[str, str] | None = None,
@@ -56,10 +61,7 @@ class AuthPIAdmin:
56
61
  raise ConfigurationError("Either api_key or access_token is required")
57
62
  if api_key and access_token:
58
63
  raise ConfigurationError("Provide either api_key or access_token, not both")
59
- if access_token and not account_id:
60
- raise ConfigurationError("account_id is required when using access_token")
61
64
 
62
- resolved_account_id = account_id or "me"
63
65
  self._http = HttpClient(
64
66
  base_url=base_url,
65
67
  api_key=api_key,
@@ -70,13 +72,23 @@ class AuthPIAdmin:
70
72
  default_headers=default_headers,
71
73
  retries=retries,
72
74
  )
73
- self._base_path = f"/v1/accounts/{resolved_account_id}"
75
+ # Without an explicit account_id, paths carry a placeholder that the
76
+ # HttpClient substitutes at request time after resolving GET /v1/me once.
77
+ self._base_path = f"/v1/accounts/{account_id or UNRESOLVED_ACCOUNT_ID}"
74
78
 
75
79
  @property
76
80
  def http(self) -> HttpClient:
77
81
  """The underlying HTTP client (for advanced use)."""
78
82
  return self._http
79
83
 
84
+ async def whoami(self) -> dict[str, Any]:
85
+ """Who does the Core API consider this credential to be?
86
+
87
+ Returns the credential type and the verified accounts it can act on
88
+ (``GET /v1/me``).
89
+ """
90
+ return await self._http.whoami()
91
+
80
92
  # --- Account-level resource accessors ---
81
93
 
82
94
  @cached_property
@@ -85,9 +97,14 @@ class AuthPIAdmin:
85
97
  return AccountsResource(self._http, "/v1/accounts")
86
98
 
87
99
  @cached_property
88
- def api_keys(self) -> ApiKeysResource:
100
+ def api_keys(self) -> AccountApiKeysResource:
89
101
  """Manage account-level API keys."""
90
- return ApiKeysResource(self._http, f"{self._base_path}/api-keys")
102
+ return AccountApiKeysResource(self._http, f"{self._base_path}/api-keys")
103
+
104
+ @cached_property
105
+ def credits(self) -> CreditsResource:
106
+ """Account credit balance, ledger, and top-ups."""
107
+ return CreditsResource(self._http, f"{self._base_path}/credits")
91
108
 
92
109
  @cached_property
93
110
  def domains(self) -> DomainsResource:
@@ -110,9 +127,9 @@ class AuthPIAdmin:
110
127
  return NotesResource(self._http, f"{self._base_path}/notes")
111
128
 
112
129
  @cached_property
113
- def tokens(self) -> TokensResource:
130
+ def tokens(self) -> AccountTokensResource:
114
131
  """Manage account-level tokens."""
115
- return TokensResource(self._http, f"{self._base_path}/tokens")
132
+ return AccountTokensResource(self._http, f"{self._base_path}/tokens")
116
133
 
117
134
  @cached_property
118
135
  def webhooks(self) -> WebhooksResource:
@@ -0,0 +1,38 @@
1
+ """Generated exports — DO NOT EDIT."""
2
+ # ruff: noqa: F401,F403
3
+
4
+ from .models import *
5
+ from .resources.account_api_keys import AccountApiKeysResource
6
+ from .resources.account_tokens import AccountTokensResource
7
+ from .resources.accounts import AccountsResource
8
+ from .resources.agent_verifiers import AgentVerifiersResource
9
+ from .resources.agents import AgentsResource
10
+ from .resources.approvals import ApprovalsResource
11
+ from .resources.auth_methods import AuthMethodsResource
12
+ from .resources.clients import ClientsResource
13
+ from .resources.credits import CreditsResource
14
+ from .resources.deliveries import DeliveriesResource
15
+ from .resources.domains import DomainsResource
16
+ from .resources.events import EventsResource
17
+ from .resources.groups import GroupsResource
18
+ from .resources.invitations import InvitationsResource
19
+ from .resources.issuer_sessions import IssuerSessionsResource
20
+ from .resources.issuer_tokens import IssuerTokensResource
21
+ from .resources.issuers import IssuersResource
22
+ from .resources.members import MembersResource
23
+ from .resources.notes import NotesResource
24
+ from .resources.organization_api_keys import OrganizationApiKeysResource
25
+ from .resources.organizations import OrganizationsResource
26
+ from .resources.payment_methods import PaymentMethodsResource
27
+ from .resources.sso import SsoResource
28
+ from .resources.trusted_devices import TrustedDevicesResource
29
+ from .resources.user_sessions import UserSessionsResource
30
+ from .resources.user_tokens import UserTokensResource
31
+ from .resources.user_verifiers import UserVerifiersResource
32
+ from .resources.users import UsersResource
33
+ from .resources.webhooks import WebhooksResource
34
+ from .scopes.agent_scope import AgentScope
35
+ from .scopes.issuer_scope import IssuerScope
36
+ from .scopes.organization_scope import OrganizationScope
37
+ from .scopes.user_scope import UserScope
38
+ from .scopes.webhook_scope import WebhookScope