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.2.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.2.0-py3-none-any.whl (17.7 kB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for lars_kluijtmans_admin_sdk-0.2.0.tar.gz
Algorithm Hash digest
SHA256 5e4be5b1cf35a5fee6ae0f9a3ff5b924ef617a1b3d6b9c1b708ea5e9801afa27
MD5 59446b013f01a0151388b6b5c8e82849
BLAKE2b-256 ce7e3fbf22626cb257f7358cbb587cf8ca86f4a17e65c37992f30c678dba6aad

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for lars_kluijtmans_admin_sdk-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 24771b4e7742bbf525e5cf79660a08e22901feec94b7d8bfd4a114d309566ab5
MD5 bce378cbb6fa0042c72b75c2c1dd2f64
BLAKE2b-256 41895b110da443889d39062d43f722d10c7ecb4824779230fed2f2f692cf2a3a

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