Skip to main content

Allowly Python SDK

Async Python client for the Allowly runtime API.

MCP middleware ships inside this SDK: pip install 'allowly[fastmcp]', then from allowly.mcp import AllowlyMCPMiddleware. In TypeScript, it lives in the separate @allowly/mcp package.

Subject authorization pattern

Do not send raw user/customer PII to Allowly receipts unless you intentionally want it in your audit trail. Create one authorization per subject, store the returned authorization ID in your own app database, and use that ID for later checks.

import asyncio
import os

from allowly import Allowly


async def main() -> None:
    async with Allowly(
        api_key=os.environ["ALLOWLY_API_KEY"],
        base_url=os.getenv("ALLOWLY_API_URL", "https://api.allowly.ai"),
    ) as allowly:
        # Your app creates a stable internal subject ID.
        subject_id = "subject_abc123"

        # Store this in your app table, for example:
        # allowly_authorizations(subject_id, policy_id, allowly_authorization_id, status)
        authorization = await allowly.authorizations.create(
            user_id=f"subject:{subject_id}",
            policy_id="research_agent",
            metadata={"source": "import"},
        )

        # Before the agent acts, check whether this action is allowed.
        decision = await allowly.check(
            authorization_id=authorization.authorization_id,
            actions=["web.search"],
            resource=f"subject:{subject_id}",
            context={"stage": "research"},
        )

    if decision.results["web.search"].decision != "allow":
        raise RuntimeError("Action is not authorized")


asyncio.run(main())

Local development against the documented Caddy endpoint requires the edge token that Cloudflare injects for public traffic. Pass it explicitly:

Allowly(
    api_key=os.environ["ALLOWLY_API_KEY"],
    base_url="http://localhost:8443",
    dangerously_allow_insecure_base_url=True,
    edge_token=os.environ["ALLOWLY_EDGE_TOKEN"],
)

The token is only sent when provided; never set it for the public API.

Send JSON through a private SEAL webhook

Copy the private URL from the dashboard's SEAL page. The URL is the only credential this client sends; it does not use an ordinary API key.

import asyncio
import os

from allowly import SealWebhookClient


async def seal_event(raw_json: str, event_id: str):
    async with SealWebhookClient(os.environ["ALLOWLY_SEAL_WEBHOOK_URL"]) as webhook:
        delivery = await webhook.send(
            raw_json,
            idempotency_key=event_id,
            type="invoice",
            reference="INV-1042",
            statement="Approved for payment",
        )
        while delivery.status in {"received", "signing"}:
            await asyncio.sleep(1)
            delivery = await webhook.get_delivery(delivery.attempt_id)
        if delivery.status != "sealed":
            raise RuntimeError(delivery.error_code or "SEAL delivery failed")
        return delivery.receipt, await webhook.get_keys()

The webhook processes your JSON to create a fingerprint; Allowly stores the fingerprint and signed receipt. Keep the original record in your workflow. Receipt details are sent in the three explicit Allowly-Seal-* headers. Their values must use printable ASCII, may contain interior spaces, and must not have leading or trailing whitespace. The client rejects invalid values instead of changing them. Direct API metadata still supports its existing Unicode values. When a signed receipt is present, the client returns its signed metadata and rejects a conflicting top-level delivery projection. Treat the full URL like a password and keep it out of logs, tickets, and source control. Regenerating or disabling it stops the old URL. Delivery associations and status remain available for 7 days; preserve signed receipts and keys under your own retention policy. With no idempotency_key, retrying after a lost response can create another seal.

Seal a JSON record with local hashing

seal hashes strict raw JSON in your process, sends only its digest to Allowly, and waits for the full signed receipt. Generate and persist request_id in your workflow so a retry recovers the same seal:

import uuid

request_id = str(uuid.uuid4())
sealed = await allowly.seal(
    raw_json,
    request_id=request_id,
    metadata={"source": "invoice-workflow"},
)
save_beside_record(sealed.receipt)

Use seal_value(parsed_json, ...) only when the original JSON text is no longer available. A parsed value cannot reveal duplicate object names or the original number spelling, so seal is the safer input boundary.

To verify later, preserve the authenticated workspace_id response and a key document fetched from Allowly through an authenticated or previously trusted source. Keep the signature and record checks separate:

from allowly.verify import load_keys_from_json, verify_seal_json

result = verify_seal_json(
    raw_json,
    sealed.receipt,
    load_keys_from_json(keys_doc),
    expected_workspace_id=sealed.workspace_id,
    trusted_key_fingerprints=configured_key_fingerprints,
)
assert result.signature_verified
assert result.record_matches

Inline authorization creation requires agent_id, actions, and expires_at. Policy-based creation uses policy_id instead and rejects inline action or decision-override fields.

Unavailable checks fail closed unless an action is explicitly mapped to "fail_open" with fallback_by_action. Unmapped actions always fail closed.

For actions that need third-party approval, define the escalation rule on the agent policy, create the authorization from that policy_id, and then resolve returned escalation results with await allowly.escalations.approve(escalation_id, resolved_by="manager:123") or reject(...), then re-check before running the action.

If you need lookup by email later, import from_email from allowly.identifiers and store from_email(email, pepper=APP_PII_PEPPER). The helper trims and lowercases only, prefixes the result with email_hmac:v1, and never sends the raw email or pepper to Allowly. Keep the pepper stable and backed up; changing it changes derived user IDs. Keep raw names, emails, documents, and profile URLs out of Allowly receipts unless those fields are intentionally part of your audit record.

Do not add raw HTTP fallbacks in application code for APIs the SDK is missing. Patch this SDK first, then use the typed client from the app. That keeps the integration examples honest and makes SDK gaps visible early.

Offline receipt verification

Install allowly[verifier] to hash SEAL records and verify signed receipts locally. The extra uses allowly-receipt-format>=4.1.0,<5.0.0, which verifies receipt wire format 4 (the package major equals the wire format). alg and key_id are signed top-level fields, and signature is the base64url string.

Wire format 4 also supports daily receipt.checkpoint commitments. For an untrusted receipt bundle, pass caller-trusted key fingerprints to verify_receipt; a fingerprint copied from that same bundle is not a trust anchor.

Key-document fetching requires HTTPS by default. For the documented local Caddy endpoint only, pass dangerously_allow_insecure_base_url=True and its edge_token to fetch_keys_doc, matching the client options above.

Metadata

Release files for allowly 0.5.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 allowly 0.5.0
File Size Uploaded Provenance Actions
allowly-0.5.0.tar.gz 119.7 kB With provenance Details for allowly-0.5.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for allowly 0.5.0
File Interpreter ABI Platform Actions
allowly-0.5.0-py3-none-any.whl Python 3 none any Details for allowly-0.5.0-py3-none-any.whl

Total release size: 142.6 kB

Release files / allowly-0.5.0.tar.gz

Download URL allowly-0.5.0.tar.gz
Size 119.7 kB
Tags Source
SHA-256 checksum
How to use checksums
8d9b7fb6231b4098019cc08ad37542d7ff5a793cb06c671043c3a445cfd37bf1
BLAKE2b-256 checksum
How to use checksums
795faa8a4c980f3ec0d7a76090985f05a3a143cdad6576d906f42d76826fbecd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 16, 2026.

Transparency log

Release files / allowly-0.5.0-py3-none-any.whl

Download URL allowly-0.5.0-py3-none-any.whl
Size 22.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1e6ea2a26389e9b2a06e84a07b0df931b71d2a0829a74cf023c42e879be8b998
BLAKE2b-256 checksum
How to use checksums
c07540625df9e397880c0e15a39b7835f846fce40a964625eefaed3ad6b00408
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 16, 2026.

Transparency log

Security for allowly 0.5.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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for allowly-0.5.0.tar.gz

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 Sep 16, 2026.

Transparency log

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