Skip to main content

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

  1. Create a client for the project (Management console → project → Clients, or the API) and enable the client_credentials grant on it. Keep the client_id + secret.
  2. 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>" }
    
    (Needs clients:write.) A client with no service role can mint a token but is denied everything.
  3. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lars_kluijtmans_admin_sdk-0.1.0.tar.gz (23.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lars_kluijtmans_admin_sdk-0.1.0-py3-none-any.whl (17.7 kB view details)

Uploaded Python 3

File details

Details for the file lars_kluijtmans_admin_sdk-0.1.0.tar.gz.

File metadata

File hashes

Hashes for lars_kluijtmans_admin_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 55f3e7c8c819a8792a1c5c77cfeaa1099720c9edb0615f415cce2a5651ead25e
MD5 f92d1d6e0773f90053a76e4f32c35886
BLAKE2b-256 943a2dc79695df174101b116e8be924f552ac3cfb9a6250b321c75b96c4184d2

See more details on using hashes here.

File details

Details for the file lars_kluijtmans_admin_sdk-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for lars_kluijtmans_admin_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b16bc343f8b596294f1485d826aacc43b754fd83d19d4d1c1dbde8241eb7e722
MD5 f6baf31ab4e0a059b1fb13f0bb1344cf
BLAKE2b-256 f538c46ee87f0518b81d78f849f1b9d2ced28d2d78a87a6765948fd1a61fa261

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page