Skip to main content

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).

Metadata

Release files for lars-kluijtmans-admin-sdk 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lars-kluijtmans-admin-sdk 0.2.0
File Size Uploaded Provenance Actions
lars_kluijtmans_admin_sdk-0.2.0.tar.gz 23.4 kB Without provenance Details for lars_kluijtmans_admin_sdk-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for lars-kluijtmans-admin-sdk 0.2.0
File Interpreter ABI Platform Actions
lars_kluijtmans_admin_sdk-0.2.0-py3-none-any.whl Python 3 none any Details for lars_kluijtmans_admin_sdk-0.2.0-py3-none-any.whl

Total release size: 41.2 kB

Release files / lars_kluijtmans_admin_sdk-0.2.0.tar.gz

Download URL lars_kluijtmans_admin_sdk-0.2.0.tar.gz
Size 23.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5e4be5b1cf35a5fee6ae0f9a3ff5b924ef617a1b3d6b9c1b708ea5e9801afa27
BLAKE2b-256 checksum
How to use checksums
ce7e3fbf22626cb257f7358cbb587cf8ca86f4a17e65c37992f30c678dba6aad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Release files / lars_kluijtmans_admin_sdk-0.2.0-py3-none-any.whl

Download URL lars_kluijtmans_admin_sdk-0.2.0-py3-none-any.whl
Size 17.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
24771b4e7742bbf525e5cf79660a08e22901feec94b7d8bfd4a114d309566ab5
BLAKE2b-256 checksum
How to use checksums
41895b110da443889d39062d43f722d10c7ecb4824779230fed2f2f692cf2a3a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Security for lars-kluijtmans-admin-sdk 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page