Skip to content

Python SDK

axowl-sdk is the backend SDK for Python services: it verifies the access token an end user presents to your API and tells you who they are and what they may do. It mirrors @axowl/sdk-backend (Node) and Axowl.Sdk.Identity.Client (.NET). Python 3.9+, one dependency (PyJWT[crypto]).

Terminal window
pip install axowl-sdk # + "axowl-sdk[fastapi]" for the FastAPI dependency
from axowl import AxowlConfig, verify_token, has_permission, AxowlAuthError
config = AxowlConfig(
org_slug="my-org", # your Axowl organization
audience="app_my_main", # optional: your application key — rejects tokens minted for another app
base_url="https://api.axowl.com", # default
)
try:
ctx = verify_token(bearer_token, config)
except AxowlAuthError as e:
... # e.reason: missing_token | invalid_token | expired | signature | issuer | audience | jwks
ctx.user_id, ctx.email, ctx.org_slug, ctx.connected_id, ctx.is_employee
ctx.permissions # ["wallet.read", "report.*"]
has_permission(ctx.permissions, "report.monthly") # True — wildcards honoured

What is checked, and against what:

CheckSource
Signature (RS256){base_url}/api/public/orgs/{org_slug}/.well-known/jwks.json — cached 10 min, re-fetched on an unknown kid
issmust equal {base_url}/api/public/orgs/{org_slug} — what Axowl writes into every end-user token
audonly when audience is configured; then it must equal your application key
exp / nbfenforced; leeway_seconds on the config absorbs clock drift

The returned AxowlContext carries every claim (ctx.token) plus typed fields: user_id (sub), email, org_id, org_slug, app_key, app_group_id, connected_id (the user’s org badge), type ("enduser"), is_employee, and permissions — the token stores permissions as a JSON string; the SDK hands you a list.

from fastapi import Depends, FastAPI
from axowl import AxowlConfig, AxowlContext
from axowl.fastapi import AxowlAuth
auth = AxowlAuth(AxowlConfig(org_slug="my-org", audience="app_my_main"))
app = FastAPI()
@app.get("/wallet")
def wallet(ctx: AxowlContext = Depends(auth)):
return {"user": ctx.email}
@app.post("/wallet/withdraw")
def withdraw(ctx: AxowlContext = Depends(auth.require("wallet.withdraw"))):
...

A missing or invalid token answers 401 {"error": …, "reason": …}; a missing scope answers 403 {"error": …, "required": [...]} — the same shapes as the Express middleware. Any other framework: extract_bearer_token(request.headers["Authorization"]) then verify_token.

The JWT fast path cannot see a permission revoked after the token was issued. For decisions that must, ask Axowl with your org API key (ah_live_…, from Organization → API keys):

from axowl import AxowlIdentityClient
identity = AxowlIdentityClient(api_key="ah_live_...", base_url="https://api.axowl.com")
res = identity.introspect(bearer_token)
# IntrospectResult(active, principal(end_user_id, organization_id, connected_id, is_employee,
# email, display_name, application_key, permissions), expires_at, issued_at)
chk = identity.check_permission(bearer_token, "wallet.withdraw")
# PermissionCheckResult(granted, matched_scopes, reason)

These call POST /v1/identity/introspect and POST /v1/identity/check-permission — the REST form of the gRPC IdentityService described on the Identity SDK page. Both are synchronous and stdlib-only; run them in a thread from async code.

Identical to every other Axowl SDK and to the server:

PatternScopeMatch
sap.fi.document.postsap.fi.document.postyes
sap.fi.*sap.fi.document.postyes
*anythingyes
sap.fisap.fi.document.postno — a prefix without * is not a wildcard

has_permission, has_all_permissions, has_any_permission and match_scope are exported for use outside a request context.