Skip to main content

authpi-admin

Official Python Admin SDK for the AuthPI Core API.

Requirements: Python 3.11+, async-only (httpx + asyncio)

Stability

authpi-admin follows semantic versioning. Public imports, constructor options, generated request and response models, resource accessors, and documented method behavior are stable across 1.x; incompatible changes will ship in a new major version.

Installation

pip install authpi-admin

Quick Start

from authpi_admin import AuthPIAdmin
from authpi_admin.types import WebhookEventType

async with AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx") as admin:
    # List issuers
    page = await admin.issuers.list(limit=10)
    print(page.data)

    # Scope into an issuer and manage users
    users = await admin.issuer("i_xxx").users.list()

    # Auto-paginate
    async for user in admin.issuer("i_xxx").users.list_all():
        print(user)

    # Create a user
    user = await admin.issuer("i_xxx").users.create({
        "username_type": "email",
        "username": "alice@example.com",
        "profile": {"first_name": "Alice", "last_name": "Smith"},
    })

    # Create a webhook with typed event subscriptions
    webhook = await admin.webhooks.create({
        "name": "Lifecycle events",
        "url": "https://example.com/webhooks/authpi",
        "auth": {"type": "signature"},
        "events": [
            WebhookEventType.ORGANIZATION_CREATED,
            WebhookEventType.USER_CREATED,
        ],
    })

Generated response models, request TypedDicts, and enums are exported from authpi_admin.types. Most calls can use dict literals directly; import request types only when you want to annotate a reusable payload:

from authpi_admin.types import CreateWebhookInput, WebhookEventType

payload: CreateWebhookInput = {
    "name": "Lifecycle events",
    "url": "https://example.com/webhooks/authpi",
    "auth": {"type": "signature"},
    "events": [WebhookEventType.USER_CREATED],
}
await admin.webhooks.create(payload)

Authentication

API Key (default)

API keys are issued as an id + secret pair — both parts are shown once when you create the key in the dashboard. The SDK sends them as HTTP Basic credentials (key_id:key_secret):

admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx")

Bearer Token

For server-side applications authenticating on behalf of a user session:

admin = AuthPIAdmin(
    access_token="tok_xxx",
    account_id="acc_xxx",
)

With optional token refresh callback:

async def refresh():
    new_tokens = await my_refresh_logic()
    return {"access_token": new_tokens.access_token}

admin = AuthPIAdmin(
    access_token="tok_xxx",
    account_id="acc_xxx",
    on_token_expired=refresh,
)

When on_token_expired is provided, the SDK calls it on 401 responses and retries the request with the new token. Concurrent 401s are deduplicated — only one refresh runs at a time.

Account resolution

account_id is optional. When omitted, the SDK resolves it once via GET /v1/me on the first request and caches it — an API key always maps to exactly one account:

async with AuthPIAdmin(api_key=("key_xxx", "your_key_secret")) as admin:
    issuers = await admin.issuers.list()  # resolves the account transparently

If the credential can act on zero or multiple accounts (possible with user bearer tokens), the SDK raises a ConfigurationError naming the choices — pass account_id explicitly in that case.

You can also ask directly who the API considers you to be:

me = await admin.whoami()
# {"type": "api_key", "key_id": "key_...", "issuer_id": "i_...", "accounts": [{"account_id": ..., "org_id": ..., "scopes": [...]}]}

Scoped Client Pattern

The SDK mirrors the API's resource hierarchy. Navigate with chained accessors:

# Account-level resources
await admin.issuers.list()
await admin.webhooks.create({
    "name": "Lifecycle events",
    "url": "https://...",
    "auth": {"type": "signature"},
    "events": [WebhookEventType.USER_CREATED],
})
await admin.events.list(limit=50)

# Issuer scope
iss = admin.issuer("i_xxx")
await iss.users.list()
await iss.agents.create({"name": "bot"})
await iss.clients.list()
await iss.organizations.list()

# User scope (nested under issuer)
org = admin.issuer("i_xxx").organization("org_xxx")
await org.members.list()
await org.sso.add_domain(domain="acme.com")

usr = admin.issuer("i_xxx").user("usr_xxx")
await usr.get()
await usr.sessions.list()
await usr.tokens.list()
await usr.trusted_devices.list()
await usr.verifiers.list()

# Webhook scope
wh = admin.webhook("wh_xxx")
await wh.get()
await wh.deliveries.list()

Pagination

List endpoints return a Page with cursor-based pagination:

# Manual pagination
page = await admin.issuer("i_xxx").users.list(limit=25)
print(page.data)        # list[dict]
print(page.has_more)    # bool
print(page.next_cursor) # str | None

# Fetch next page
if page.has_more:
    next_page = await admin.issuer("i_xxx").users.list(
        limit=25, cursor=page.next_cursor
    )

# Auto-pagination (yields individual items across all pages)
async for user in admin.issuer("i_xxx").users.list_all():
    print(user)

Retries

Read-only requests (GET, HEAD, OPTIONS) are automatically retried on 429, 502, 503, and 504 responses with exponential backoff. The Retry-After header is respected when present.

# Default: retries enabled (3 attempts, 1s base delay, exponential backoff)
admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx")

# Disable retries
admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), retries=False)

# Custom retry config
admin = AuthPIAdmin(
    api_key=("key_xxx", "your_key_secret"),
    account_id="acc_xxx",
    retries={"limit": 5, "delay": 0.5, "backoff": "linear"},
)

Mutations (POST, PATCH, DELETE) are never retried automatically. Use idempotency keys and handle retries explicitly for writes.

ETags & Optimistic Concurrency

GET responses include an _etag field. Pass it back on updates to prevent overwriting concurrent changes:

user = await admin.issuer("i_xxx").user("usr_xxx").get()

# Conditional update — fails with PreconditionFailedError if modified
await admin.issuer("i_xxx").users.update(
    "usr_xxx",
    {"profile": {"display_name": "Bob"}},
    if_match=user.get("_etag"),
)

Error Handling

The SDK maps HTTP status codes to specific error classes:

from authpi_admin import (
    NotFoundError,
    ValidationError,
    AuthenticationError,
    RateLimitError,
    PreconditionFailedError,
)

try:
    await admin.issuer("i_xxx").user("usr_xxx").get()
except NotFoundError:
    print("User not found")
except ValidationError as err:
    print("Validation failed:", err.fields)
except RateLimitError as err:
    print(f"Retry after {err.retry_after} seconds")
except AuthenticationError:
    print("Invalid credentials")

Error Hierarchy

Error Status Extra Fields Retryable
ApiError error, error_description, status_code, retryable, reference, raw_body
ValidationError 400, 422 fields No
AuthenticationError 401 No
ForbiddenError 403 No
NotFoundError 404 No
ConflictError 409 No
PreconditionFailedError 412 current_etag No
RateLimitError 429 retry_after Yes
InternalServerError 500 No
BadGatewayError 502 Yes
ServiceUnavailableError 503 Yes
GatewayTimeoutError 504 Yes
UnexpectedError other No
ClosedClientError No

Configuration

from authpi_admin import AuthPIAdmin

admin = AuthPIAdmin(
    api_key=("key_xxx", "your_key_secret"),     # or access_token instead
    account_id="acc_xxx",                       # optional — resolved via GET /v1/me when omitted
    base_url="https://api.authpi.com",          # default
    timeout=30.0,                               # default, in seconds
    default_headers={"X-Custom": "value"},      # optional extra headers
    retries=True,                               # default (or False, or dict)
)

Custom httpx Client

Inject a pre-configured httpx.AsyncClient for advanced use cases (proxies, certificates, connection pooling):

import httpx
from authpi_admin import AuthPIAdmin

async with httpx.AsyncClient(proxies="http://proxy:8080") as http:
    admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), http_client=http)
    await admin.issuers.list()

Context Manager

The SDK supports async context managers for clean resource cleanup:

async with AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx") as admin:
    await admin.issuers.list()
# httpx client is closed automatically

Or close manually:

admin = AuthPIAdmin(api_key=("key_xxx", "your_key_secret"), account_id="acc_xxx")
try:
    await admin.issuers.list()
finally:
    await admin.close()

License

MIT

Release files for authpi-admin 1.0.1

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

Source distribution (sdist)

Source distribution for authpi-admin 1.0.1
File Size Uploaded
authpi_admin-1.0.1.tar.gz 63.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for authpi-admin 1.0.1
File Interpreter ABI Platform
authpi_admin-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 127.6 kB

Release files / authpi_admin-1.0.1.tar.gz

Download URL authpi_admin-1.0.1.tar.gz
Size 63.6 kB
Tags Source
SHA-256 checksum
How to use checksums
105d4d7855453b91f9eba3247da6c7d9b1a25549f39d0b1fbfe169210cbdc32e
BLAKE2b-256 checksum
How to use checksums
37a9745c089bd6aa36960dd8074308a9c4f80083dbdd7a9f9add6b7d12cf7382
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 1, 2026.

Transparency log

Release files / authpi_admin-1.0.1-py3-none-any.whl

Download URL authpi_admin-1.0.1-py3-none-any.whl
Size 64.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a23b1484e7ce91662eb5afd20f3aa6387510262a4eaac778834421cce0927084
BLAKE2b-256 checksum
How to use checksums
6d9b8cfdf2ceeaa2cbfb79cca09cd4f86e23da60f413a096a18448f18464c453
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 1, 2026.

Transparency log

Release history Release notifications | RSS feed

1.3.1

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

This release

1.0.1 This release

2 release files

1.0.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.3.0

2 release files

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