> Markdown version of https://authpi.com/docs/sdks/idp-python/ — fetch the complete AuthPI docs index at https://authpi.com/llms.txt to discover all available pages.

# IdP SDK — Python

Add AuthPI login to Python backends with authpi-idp — the OIDC authorization-code flow with PKCE, token refresh, and per-organization authorization checks.

[`authpi-idp`](https://pypi.org/project/authpi-idp/) is the official Python SDK for authenticating users against your AuthPI issuer: it drives the OIDC authorization-code flow with PKCE, exchanges and refreshes tokens, and gives you an authenticated *agent* object with the user's identity and per-organization permissions.

**Requirements:** Python 3.11+. All token operations are async.

## Install

```bash
pip install authpi-idp
```

## Initialize

```python
from authpi_idp import IdpClient

idp = IdpClient(
    issuer_url="https://idp.authpi.com/i_4r8w2k9m5x1p7q3e6t0y2u4i8",
    client_id="cli_xxx",
    client_secret="...",  # omit for public clients (SPAs, native apps)
    redirect_uri="https://app.example.com/callback",
)
```

## The login flow

**1. Send the user to AuthPI.** `create_authorization_url` is synchronous and generates the PKCE verifier, `state`, and `nonce` for you — store them in the user's session, then redirect:

```python
auth = idp.create_authorization_url(scopes=["openid", "profile", "email"])

session["oauth"] = {
    "code_verifier": auth.code_verifier,
    "state": auth.state,
    "nonce": auth.nonce,
}
return redirect(auth.url)
```

**2. Handle the callback.** After checking that the returned `state` matches the one you stored, exchange the code for an authenticated agent. The exchange goes directly to the token endpoint over TLS, and PKCE binds it to the verifier from step 1:

```python
agent = await idp.exchange_code(code, session["oauth"]["code_verifier"])

session["tokens"] = agent.tokens.model_dump()
```

**3. Use the agent.** It carries the user's identity and organization memberships, with an authorization helper that checks scopes within an organization:

```python
if agent.has_access_in("org_0kfz3m8q1w5e9r2t6y4u7i3o5", "write", "projects"):
    ...  # the user can write to projects in that organization
```

## Sessions and refresh

Rebuild an agent from stored tokens on subsequent requests — `create_agent` refreshes expired access tokens automatically and hands you the rotated tokens through `on_refresh`:

```python
agent = await idp.create_agent(
    session["tokens"],
    on_refresh=lambda new_tokens: session.update({"tokens": new_tokens}),
    on_refresh_error=lambda error: handle_session_expired(error),
)
```

Refresh tokens rotate on use, so always persist what `on_refresh` gives you. Organization claims in refreshed tokens are recomputed at every refresh — membership changes propagate within the access-token TTL ([the propagation contract](/docs/guides/org-lifecycle)).

## Next steps

- [Server-side auth with the TypeScript SDK](/docs/quickstarts/typescript-backend) — the same flow, step by step (the concepts transfer directly)
- [Token claims reference](/docs/reference/token-claims) — what's inside `agent.tokens`
- [Validate tokens in your API](/docs/guides/validate-tokens) — verifying these tokens in downstream services
- [Package on PyPI](https://pypi.org/project/authpi-idp/) — full README, framework integration notes, and advanced options