Python admin SDK for the Platform Management API
Project description
lars-kluijtmans-admin-sdk
A Python admin SDK for the Platform auth-domain APIs — modelled on the Firebase Authentication Admin SDK. From a server-side program: read projects, clients and providers, manage a project's custom OAuth scopes, and read/update users (active state, custom claims, password-reset/verification emails).
The users/projects/clients/providers/scopes surface is served by the auth-api (/auth/v1, default
http://127.0.0.1:8050); client.notifications and client.logs / client.usage call the
notification-api and logs-api. All accept the same M2M token.
pip install lars-kluijtmans-admin-sdk
The import package stays platform_admin (from platform_admin import AdminClient).
Requires Python 3.10+.
Authenticating
Two credential modes.
M2M (recommended for servers)
A service client with the client_credentials grant. The SDK runs the grant against the
Login API, then caches and refreshes the token for you:
from platform_admin import AdminClient
client = AdminClient.from_client_credentials(
login_api_url="https://login.example.com",
auth_url="https://auth.example.com", # auth-api; default http://127.0.0.1:8050
client_id="client_xxx",
client_secret="…",
)
Bring-your-own token
Attach an admin access token you already hold (e.g. from a logged-in session):
client = AdminClient(auth_url="https://auth.example.com", token=ACCESS_TOKEN)
What a credential may do is governed by RBAC: a user token uses that user's role; a service client uses the role assigned to it (see Setting up a service client below).
AdminClient is a context manager and closes its HTTP connection on exit:
with AdminClient.from_client_credentials(...) as client:
...
Using it
# Projects
project = client.projects.get(project_id)
projects = client.projects.list(company_id)
# Clients & providers
clients = client.clients.list(project_id)
client.clients.set_service_role(project_id, client_id, role_id) # authorize an m2m client (None clears)
provider = client.providers.catalog() # platform catalog
enabled = client.providers.list(project_id) # per-project, with enable state
# Scopes — a project's custom OAuth scopes (read: scopes:read, mutate: scopes:write)
scopes = client.scopes.list(project_id)
scope = client.scopes.create(project_id, "invoices:read", description="Read invoices")
client.scopes.update(project_id, scope.id, description="Read all invoices") # only description is mutable
client.scopes.delete(project_id, scope.id)
# Users — read
users = client.users.list(project_id, search="ann", status="active") # one page
for user in client.users.iter(project_id): # all pages
print(user.email, user.is_active)
user = client.users.get(project_id, user_id)
# Users — update (active state, claims, and profile fields)
client.users.set_active(project_id, user_id, False)
client.users.set_claims(project_id, user_id, {"tier": "gold"})
client.users.set_profile(project_id, user_id, {"display_name": "Ann", "language": "nl"})
# Firebase-style: apply whichever fields you pass (profile fields accept None to clear).
client.users.update(project_id, user_id, active=True, claims={"tier": "silver"},
display_name="Ann", language="nl", profile_picture="https://cdn/x.png")
# Users — remediation
client.users.send_password_reset(project_id, user_id)
client.users.resend_verification(project_id, user_id)
client.users.force_logout(project_id, user_id)
Every model exposes .raw (the full API payload) so new/unknown fields are always reachable.
The admin can edit display name, language, and profile picture (
set_profile/update), flip active state, set claims, and trigger the reset/verification emails. Email and password changes remain self-service on the Login API — they are not part of the admin surface.
Notifications
client.notifications drives the notification-api — a separate service at its own base URL
(default http://127.0.0.1:8020, override with notifications_url=). It reuses the same M2M token;
your service client's role needs notifications:send / notifications:read /
notifications:configure.
client = AdminClient.from_client_credentials(
login_api_url="https://login.example.com",
auth_url="https://auth.example.com", # default http://127.0.0.1:8050
notifications_url="https://notify.example.com", # default http://127.0.0.1:8020
client_id="client_xxx",
client_secret="…",
)
# Send (channel = "email" | "sms" | "inapp" | "push"; for inapp/push `to` is the user's id)
result = client.notifications.send(
"email", "user@example.com", template_key="welcome", context={"name": "Ann"}, project_id=project_id
)
print(result.message_id, result.status, result.provider)
# Delivery history — one page (limit/offset) or iterate every event (recipients are hashed)
page = client.notifications.list_messages(channel="email", status="sent") # page.items / page.total
for event in client.notifications.iter_messages():
print(event.channel, event.status, event.recipient_hash)
# Settings (provider secrets are write-only; a blank/absent secret keeps the stored one)
settings = client.notifications.get_settings(project_id) # None if unset
client.notifications.update_settings(project_id=project_id, sender_name="Acme",
email={"provider": "smtp", "password": "…"})
# Custom templates
client.notifications.templates.list(project_id=project_id)
tpl = client.notifications.templates.create("welcome", "Hi {{name}}", subject="Welcome")
client.notifications.templates.update(tpl.id, "Hello {{name}}", subject="Hi")
client.notifications.templates.delete(tpl.id)
Logs & usage
client.logs and client.usage drive the logs-api — a separate service at its own base URL
(default http://127.0.0.1:8030, override with logs_url=). It reuses the same M2M token; your
service client's role needs logs:write / logs:read / usage:read.
client = AdminClient.from_client_credentials(
login_api_url="https://login.example.com",
auth_url="https://auth.example.com", # default http://127.0.0.1:8050
logs_url="https://logs.example.com", # default http://127.0.0.1:8030
client_id="client_xxx",
client_secret="…",
)
# Ingest a batch of 1–500 application log events (logs:write); returns the accepted count.
result = client.logs.write([
{"level": "info", "category": "billing", "message": "invoice generated",
"metadata": {"invoice_id": "inv_1"}, "project_id": project_id},
])
print(result.accepted)
# Query the merged log/audit stream (logs:read). `start`/`end` are ISO-8601 (sent as from/to);
# source = "logs" | "audit" | "all". One page (limit/offset) or iterate every entry.
page = client.logs.query(level="info", category="billing", source="all",
start="2026-01-01T00:00:00Z", end="2026-02-01T00:00:00Z")
for entry in client.logs.iter(source="audit"):
print(entry.kind, entry.timestamp, entry.message, entry.action)
# Aggregated usage (usage:read): totals + a per-bucket timeseries. bucket = "day" | "hour".
usage = client.usage.summary(start="2026-01-01T00:00:00Z", end="2026-02-01T00:00:00Z", bucket="day")
print(usage.summary) # {login_success, login_failure, tokens_issued, active_sessions}
for point in usage.timeseries:
print(point.bucket_start, point.metrics)
Setting up a service client
- Create a client for the project (Management console → project → Clients, or the API) and
enable the
client_credentialsgrant on it. Keep theclient_id+ secret. - Assign it a role — the permissions an m2m token from this client gets:
PUT /auth/v1/projects/{project_id}/clients/{client_id}/service-role { "role_id": "<a role id>" }
(Needsclients:write.) A client with no service role can mint a token but is denied everything. - Use
AdminClient.from_client_credentials(...)with that client's id + secret.
The service account is always scoped to its own company/project and is never a platform admin.
Multiple services (base-URL registry)
client.notifications / client.logs / client.usage don't call the auth-api — they call the
notification-api and logs-api, which are separate services at their own base URLs that accept the
same token. The client keeps a small registry of service base URLs keyed by name
(auth / notifications / logs); each resource declares which service it targets and the
transport resolves the base URL from the registry. The defaults are set from auth_url= (primary) /
notifications_url= / logs_url= on the constructor, but you can register or override any service by
name:
client.register_service("notifications", "https://notify.example.com")
client.base_url_for("logs") # -> the logs-api base URL this client routes logs/usage to
client.notifications_base_url # convenience property; same as base_url_for("notifications")
This is the SDK's module-extension seam: a new module's API surface is one registered service plus
one resource class (a Resource subclass that sets SERVICE = "<name>") — no new constructor
argument or per-service plumbing.
Errors
Failures raise typed exceptions — AuthError (401), Forbidden (403), NotFound (404),
Conflict (409), ValidationError (422), NetworkError — all subclasses of AdminError
(carrying .status and .detail):
from platform_admin import NotFound, Forbidden
try:
client.users.get(project_id, user_id)
except NotFound:
...
except Forbidden as exc:
print(exc.status, exc.detail)
See examples/ for runnable scripts (M2M and bring-your-own-token).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lars_kluijtmans_admin_sdk-0.2.0.tar.gz.
File metadata
- Download URL: lars_kluijtmans_admin_sdk-0.2.0.tar.gz
- Upload date:
- Size: 23.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e4be5b1cf35a5fee6ae0f9a3ff5b924ef617a1b3d6b9c1b708ea5e9801afa27
|
|
| MD5 |
59446b013f01a0151388b6b5c8e82849
|
|
| BLAKE2b-256 |
ce7e3fbf22626cb257f7358cbb587cf8ca86f4a17e65c37992f30c678dba6aad
|
File details
Details for the file lars_kluijtmans_admin_sdk-0.2.0-py3-none-any.whl.
File metadata
- Download URL: lars_kluijtmans_admin_sdk-0.2.0-py3-none-any.whl
- Upload date:
- Size: 17.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
24771b4e7742bbf525e5cf79660a08e22901feec94b7d8bfd4a114d309566ab5
|
|
| MD5 |
bce378cbb6fa0042c72b75c2c1dd2f64
|
|
| BLAKE2b-256 |
41895b110da443889d39062d43f722d10c7ecb4824779230fed2f2f692cf2a3a
|