> ## Documentation Index
> Fetch the complete documentation index at: https://docs.atlasent.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> atlasent — Python client for AtlaSent.

## Install

```bash theme={null}
pip install atlasent
```

Supports Python 3.9+.

## Hello, world

The canonical execution-boundary surface. `protect()` mints and
verifies a [Permit](/concepts/permit) end-to-end and returns it on
success. There is no `permitted=False` return path — if `protect()`
returns, the action is authorized.

```python theme={null}
import os
from atlasent import protect, AtlaSentDeniedError, AtlaSentError

# Picks up ATLASENT_API_KEY from the environment.
try:
    permit = protect(
        agent="agent-001",
        action="ehr.write",
        context={"environment": "production", "patient_id": "patient-42"},
    )
except AtlaSentDeniedError as exc:
    halt(exc.reason)               # policy denied / hold / escalate / verify-fail
except AtlaSentError:
    halt("fail-closed")            # transport / timeout / auth / 5xx

# Authorized end-to-end. Execute and persist permit.permit_id alongside
# your own record so the audit chain stays two-way navigable.
db.patients.update("patient-42", payload, atlasent_permit_id=permit.permit_id)
```

See [Runtime flow](/concepts/runtime-flow) for the full lifecycle and
the fail-closed matrix.

## `with_permit()` — lexically-scoped form

When the action body is a single scope, bind it to the permit's
lifetime directly. Same wire contract as `protect()`; same audit-chain
entry. The body runs **only** on a verified permit.

```python theme={null}
from atlasent import with_permit

result = with_permit(
    agent="agent-001",
    action="ehr.write",
    context={"environment": "production", "patient_id": "patient-42"},
    fn=lambda permit: db.patients.update(
        "patient-42", payload, atlasent_permit_id=permit.permit_id,
    ),
)
```

`protect()` returns the Permit to the caller; `with_permit()` binds it
to the body. Pick the form that fits the call site — both produce
identical wire calls, identical audit-chain entries, and identical
fail-closed semantics.

## `require_permit()` — descriptor form

For dangerous operations described by a richer `ProtectedAction`
(`resource_id`, `environment`). Async-only:

```python theme={null}
from atlasent import require_permit, ProtectedAction

await require_permit(
    ProtectedAction(
        action_type="database.table.drop",
        actor_id="agent:code-agent",
        resource_id="prod-db.users",
        environment="production",
        context={"reversibility": "irreversible"},
    ),
    execute=lambda: db.raw_async("DROP TABLE users"),
)
```

## Error model

```python theme={null}
from atlasent import AtlaSentDeniedError, AtlaSentError
from atlasent.exceptions import RateLimitError, StreamTimeoutError

try:
    permit = protect(agent=..., action=..., context=...)
except AtlaSentDeniedError as e:
    halt(e.reason)                 # policy said no, or permit verify-fail
except RateLimitError as e:
    backoff(e.retry_after_s)       # 429
except AtlaSentError:
    halt("fail-closed")            # transport / auth / 5xx / timeout
```

`protect()` and `with_permit()` collapse policy denial, hold,
escalate, and verification failure into `AtlaSentDeniedError`.
Inspect `exc.decision` (`"deny"` / `"hold"` / `"escalate"`) if you
need to branch.

## Inspecting the four-value decision

`protect()` and `with_permit()` raise on anything other than `allow`.
If you need to inspect `hold` / `escalate` at the call site — for
example, to route to an approval queue — drop down to the raw
`evaluate()` primitive:

```python theme={null}
from atlasent import evaluate

result = evaluate(action_type="ehr.write", actor_id="agent-001", context={...})
match result.decision:
    case "allow":    ...  # call verify_permit_by_id(), then execute
    case "deny":     ...  # surface result.deny_reason
    case "hold":     ...  # wait for the approval webhook
    case "escalate": ...  # routed to a human reviewer
```

`evaluate()` does not verify the permit for you. Callers using the
raw primitive must verify before executing — see [Verify a permit
explicitly](/quickstart/verify-permit).

## Async

```python theme={null}
from atlasent import AsyncAtlaSentClient
from atlasent.aio import with_permit as awith_permit

async with AsyncAtlaSentClient() as client:
    result = await awith_permit(
        client,
        agent="agent-001",
        action="ehr.write",
        context={"environment": "production"},
        fn=lambda permit: do_async_work(permit.permit_id),
    )
```

## SCIM provisioning

Manage SCIM 2.0 user and group provisioning via the `ScimClient` sub-client.

```python theme={null}
from atlasent import AtlaSentClient
from atlasent.scim_client import ScimClient

client = AtlaSentClient(api_key="ask_live_...")
scim = ScimClient(client)

# List provisioned users
page = scim.users.list(org_id="org_abc", count=50)
for user in page["Resources"]:
    print(user["userName"], user["active"])

# Provision a new user
new_user = scim.users.create(org_id="org_abc", user={
    "userName": "alice@example.com",
    "active": True,
    "name": {"givenName": "Alice", "familyName": "Smith"},
    "emails": [{"value": "alice@example.com", "primary": True}],
})

# Deprovision
scim.users.delete(org_id="org_abc", user_id=new_user["id"])
```

`schemas` is injected automatically if absent. See [SCIM 2.0 provisioning guide](/enterprise/scim-admin) for IdP configuration.

## Token management

Multi-IdP token refresh via the `atlasent.auth` module. Use this in programmatic integrations that maintain long-lived access tokens.

```python theme={null}
from atlasent import AtlaSentClient
from atlasent.auth import refresh_token, refresh_with_idp, list_idp_connections

client = AtlaSentClient(api_key="ask_live_...")

# List available IdP connections
connections = list_idp_connections(client)
# [{"id": "idp_okta_prod", "name": "Okta Production", "provider": "okta", ...}]

# Refresh using the default IdP
tokens = refresh_token(client, current_refresh_token)
# {"access_token": "at_...", "refresh_token": "rt_...", "expires_in": 3600}

# Refresh against a specific IdP (multi-IdP orgs)
tokens = refresh_with_idp(client, idp_id="idp_okta_prod",
                           refresh_token=current_refresh_token)
```

## Evidence bundles

Create and download compliance evidence bundles.

```python theme={null}
from atlasent.evidence_bundle import (
    create_evidence_bundle,
    get_evidence_bundle,
    download_evidence_bundle,
)

bundle = create_evidence_bundle(
    client,
    org_id="org_abc",
    incident_id="inc_001",
    include_overrides=True,
)

# Poll until ready
while bundle["status"] == "generating":
    import time; time.sleep(2)
    bundle = get_evidence_bundle(client, "org_abc", bundle["bundle_id"])

# Download as PDF
pdf_bytes = download_evidence_bundle(
    client, "org_abc", bundle["bundle_id"], format="pdf"
)
with open("evidence.pdf", "wb") as f:
    f.write(pdf_bytes)
```

## Links

* Source: [`atlasent-sdk/python`](https://github.com/Atlasent/atlasent-sdk/tree/main/python)
* Changelog: [`/changelog/sdks#python`](/changelog/sdks#python)
* Type hints: full; `py.typed` marker shipped.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.